mybatis讲义

第一天:mybatis入门

1.1 mybatis的概述

​ mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。
采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。

1.1.1jdbc 程序的回顾

public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            //加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            //通过驱动管理类获取数据库链接
            connection =
                    DriverManager
                            .getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "root");
                                    //定义 sql 语句 ?表示占位符
                                    String sql = "select * from user where username = ?";
            //获取预处理 statement
            preparedStatement = connection.prepareStatement(sql);
            //设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的参数值
            preparedStatement.setString(1, "王五");
            //向数据库发出 sql 执行查询,查询出结果集
            resultSet = preparedStatement.executeQuery();
                //遍历查询结果集
            while(resultSet.next()){
                System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //释放资源
            if(resultSet!=null){
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(preparedStatement!=null){
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(connection!=null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
上边使用 jdbc 的原始方法(未经封装)实现了查询数据库表记录的操作。

1.1.2 jdbc 问题分析

  1. 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
  2. Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java
  3. 代码。
  4. 使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。
  5. 对结果集解析存在硬编码(查询列名),sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。

1.2 mybatis的环境搭建及入门案例

环境搭建的注意事项:
第一个:创建UserDao.xml 和 UserDao.java时名称是为了和我们之前的知识保持一致。
在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper
所以:IUserDao 和 IUserMapper是一样的
第二个:在idea中创建目录的时候,它和包是不一样的
包在创建时:com.itheima.dao它是三级结构
目录在创建时:com.itheima.dao是一级目录
第三个:mybatis的映射配置文件位置必须和dao接口的包结构相同
第四个:映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
第五个:映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名

    当我们遵从了第三,四,五点之后,我们在开发中就无须再写dao的实现类。

1.2.1 创建 maven 工程

创建mybatis-study工程,工程信息如下

<groupId>org.example</groupId>
<artifactId>mybatis-study</artifactId>
<version>1.0-SNAPSHOT</version>

1.2.2 添加 Mybatis3.4.5 的坐标

在 pom.xml 文件中添加 Mybatis3.4.5 的坐标,如下:

<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.5</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.10</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
</dependencies>

1.2.3 添加 数据库表

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` int(11) NOT NULL auto_increment,
  `username` varchar(32) NOT NULL COMMENT '用户名称',
  `birthday` datetime default NULL COMMENT '生日',
  `sex` char(1) default NULL COMMENT '性别',
  `address` varchar(256) default NULL COMMENT '地址',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;



insert  into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'),(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),(46,'老王','2018-03-07 17:37:26','男','北京'),(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');



DROP TABLE IF EXISTS `account`;

CREATE TABLE `account` (
  `ID` int(11) NOT NULL COMMENT '编号',
  `UID` int(11) default NULL COMMENT '用户编号',
  `MONEY` double default NULL COMMENT '金额',
  PRIMARY KEY  (`ID`),
  KEY `FK_Reference_8` (`UID`),
  CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;



insert  into `account`(`ID`,`UID`,`MONEY`) values (1,41,1000),(2,45,1000),(3,41,2000);



DROP TABLE IF EXISTS `role`;

CREATE TABLE `role` (
  `ID` int(11) NOT NULL COMMENT '编号',
  `ROLE_NAME` varchar(30) default NULL COMMENT '角色名称',
  `ROLE_DESC` varchar(60) default NULL COMMENT '角色描述',
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) values (1,'院长','管理整个学院'),(2,'总裁','管理整个公司'),(3,'校长','管理整个学校');



DROP TABLE IF EXISTS `user_role`;

CREATE TABLE `user_role` (
  `UID` int(11) NOT NULL COMMENT '用户编号',
  `RID` int(11) NOT NULL COMMENT '角色编号',
  PRIMARY KEY  (`UID`,`RID`),
  KEY `FK_Reference_10` (`RID`),
  CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`),
  CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `user_role`(`UID`,`RID`) values (41,1),(45,1),(41,2);

1.2.4 编写SqlMapConfig.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis的主配置文件-->
<configuration>
    <!--配置环境-->
    <environments default="mysql">
        <!--配置mysql的环境-->
        <environment id="mysql">
            <!-- 配置事务类型-->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!-- 配置连接数据库的4个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!-- 指定字符编码为UTF-8 防止中文乱码-->
                <property name="url" value="jdbc:mysql://localhost:3306/easy?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
   <mappers>
        <!-- 使用相对于类路径的资源引用 -->
         <mapper resource="org/example/dao/UserDao.xml"/>

        <!-- 使用完全限定资源定位符(URL) -->
        <!--<mapper url="file:///var/mappers/AuthorMapper.xml"/>-->

        <!-- 使用映射器接口实现类的完全限定类名 -->
        <!-- <mapper class="org.mybatis.builder.AuthorMapper"/> -->

        <!-- 将包内的映射器接口实现全部注册为映射器 -->
        <!--<package name="org.example.mapper"/>-->
    </mappers>
</configuration>

1.2.5编写 User 实体类

package org.example.domain;


import java.io.Serializable;
import java.util.Date;

/**
 * 用户的实体类
 */
public class User implements Serializable {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

1.2.6 编写持久层接口 UserDao

package org.example.dao;

import org.example.domain.User;
import java.util.List;

/**
 * UserDao 接口就是我们的持久层接口
 */
public interface UserDao {

    /**
     * 查询所有用户操作
     * @return
     */
    List<User> findAll();
}

1.2.7 编写持久层接口的映射文件 UserDao.xml

要求:
创建位置:必须和持久层接口在相同的包中。
名称:必须以持久层接口名称命名文件名,扩展名是.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.dao.UserDao">
    <!-- 配置查询所有操作 -->
    <select id="findAll" resultType="org.example.domain.User">
        select * from user
    </select>
</mapper>

1.2.8 编写测试类

package org.example.test;


import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.example.dao.UserDao;
import org.example.domain.User;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 *  mybatis 入门案例
 */
public class MybatisTest {

    /**
     * 入门案例
     * @param args
     */
    public static void main(String[] args) throws IOException {
        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建 SqlSessionFactory 的构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

        //3.使用构建者创建工厂对象 SqlSessionFactory
        SqlSessionFactory factory = builder.build(in);

        //4.使用 SqlSessionFactory 生产 SqlSession 对象
        SqlSession session = factory.openSession();

        //5.使用 SqlSession 创建 dao 接口的代理对象
        UserDao userDao = session.getMapper(UserDao.class);
        //6.使用代理对象执行查询所有方法
        List<User> users = userDao.findAll();
        for(User user : users) {
            System.out.println(user);
        }
        //7.释放资源
        session.close();
        in.close();
    }
}

第二天:mybatis基本使用

使用要求:
1、持久层接口和持久层接口的映射配置必须在相同的包下
2、持久层映射配置中 mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名
3、SQL 语句的配置标签<select>,<insert>,<delete>,<update>的 id 属性必须和持久层接口的
方法名相同。

2.1 基于代理 Dao 实现 CRUD 操作

2.1.1根据 ID 查询

在持久层接口中添加 findById 方法

/**
 * 根据Id查询用户
 */
User findById(Integer id);
<!-- 根据Id查询用户-->
<select id="findById" parameterType="java.lang.Integer" resultType="org.example.domain.User">
select * from  user where id = #{id}
</select>
在用户的映射配置文件中配置细节:
resultType 属性:
    用于指定结果集的类型。
parameterType 属性:
    用于指定传入参数的类型。
sql 语句中使用#{}字符:
    它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。
    具体的数据是由#{}里面的内容决定的。
#{}中内容的写法:
    由于数据类型是基本类型,所以此处可以随意写。

在测试类添加测试

public class MybatisTest {

    private  InputStream in;
    private  SqlSession sqlSession;
    private  UserDao userDao;

    @Before  // test方法执行之前执行
    public void init() throws IOException {
            //1.读取配置文件
             in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建 SqlSessionFactory 的构建者对象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

            //3.使用构建者创建工厂对象 SqlSessionFactory
            SqlSessionFactory factory = builder.build(in);

            //4.使用 SqlSessionFactory 生产 SqlSession 对象
            sqlSession = factory.openSession();

            //5.使用 SqlSession 创建 dao 接口的代理对象
            userDao = sqlSession.getMapper(UserDao.class);

    }

    @After // test方法执行之后执行
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //7.释放资源
        sqlSession.close();
        in.close();
    }

    
    @Test
    public void testFindAll() throws IOException {
        //6.使用代理对象执行查询所有方法
        List<User> users = userDao.findAll();
        for(User user : users) {
            System.out.println(user);
        }
    }
    
     /**
     * 测试根据id查询用户
     */
    @Test
    public void testDFindById(){
        User user = userDao.findById(49);
        System.out.println(user);
    }
}

2.1.2 保存操作

在持久层接口中添加新增方法

/**
 * 保存用户
 * @param user
 * @return 影响数据库记录的行数
 */
int saveUser(User user);

在用户的映射配置文件中配置

<!-- 保存用户-->
<insert id="saveUser">
    新增用户后,同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相
当于我们要在新增后将自动增长 auto_increment 的值返回。
<!-- 配置插入操作后,获取出入数据的id-->
    <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
        select last_insert_id();
    </selectKey>
    INSERT
    INTO
        user
        (username, address, sex, birthday)
    VALUES
        (#{username}, #{address}, #{sex}, #{birthday});
</insert>
细节:
parameterType 属性:
    代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。
sql 语句中使用#{}字符:
    它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。
    具体的数据是由#{}里面的内容决定的。
#{}中内容的写法:
    由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。
    它用的是 ognl 表达式。
ognl 表达式:
    它是 apache 提供的一种表达式语言,全称是:
Object Graphic Navigation Language
    对象图导航语言
    它是按照一定的语法格式来获取数据的。
    语法格式就是使用 #{对象.对象}的方式
        #{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user.而直接写 username。

添加测试类中的测试方法

@Test
public void testSave() throws IOException {
    User user = new User();
    user.setUsername("mybatis saveuser2");
    user.setAddress("天涯海角");
    user.setSex("男");
    user.setBirthday(new Date());
    System.out.println("保存之前:" + user);
    // 执行保存
    userDao.saveUser(user);
    System.out.println("保存之后:" + user);
}
    打开 Mysql 数据库发现并没有添加任何记录,原因是什么?
    这一点和 jdbc 是一样的,我们在实现增删改时一定要去控制事务的提交,那么在 mybatis 中如何控制事务提交呢?
    可以使用:session.commit();来实现事务提交。加入事务提交后的代码如下:
    @After // test方法执行之后执行
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //7.释放资源
        sqlSession.close();
        in.close();
    }

2.1.3 用户更新

在持久层接口中添加更新方法

/**
 * 更新用户
 * @param user
 * @return 影响数据库记录的行数
 */
int updateUser(User user);

在用户的映射配置文件中配置

<!--更新用户-->
<update id="updateUser" parameterType="org.example.domain.User">
    UPDATE user
    SET username = #{username},
        address = #{address},
        sex = #{sex},
        birthday = #{birthday}
    WHERE id = #{id}

加入更新的测试方法

/**
 * 更新测试
 */
@Test
public void testUpdate(){
    User user = new User();
    user.setId(49);
    user.setUsername("mybatis 49");
    user.setAddress("天涯海角");
    user.setSex("男");
    user.setBirthday(new Date());
    // 执行保存
    userDao.updateUser(user);
}

2.1.4 用户删除

在持久层接口中添加删除方法

/**
 * 根据id删除用户
 * @param id
 * @return 影响数据库记录的行数
 */
int deleteUser(Integer id);

在用户的映射配置文件中配置

<!-- 根据id删除用户-->
<delete id="deleteUser" parameterType="java.lang.Integer">
    delete from user where id = #{id}
</delete>

加入删除的测试方法

/**
 * 测试删除
 */
@Test
public void testDelete(){
    userDao.deleteUser(50);
}

2.1.5 用户模糊查询

在持久层接口中添加模糊查询方法

/**
 * 根据名称模糊查询用户信息
 */
List<User> findByName(String username);

在用户的映射配置文件中配置

<!-- 根据名称模糊查询用户信息 -->
<select id="findByName" parameterType="java.lang.String"  resultType="org.example.domain.User">
    select * from user where username like concat('%',#{username},'%')
</select>

加入模糊查询的测试方法

/**
 * 测试迷糊查询
 */
@Test
public void testFindByName(){
    List<User> list = userDao.findByName("小二");
    for (User user: list) {
        System.out.println(user);
    }
}

2.1.6 查询使用聚合函数

在持久层接口中添加模糊查询方法

/**
 * 查询总用户数
 * @return
 */
int findTotal();

在用户的映射配置文件中配置

<!--查询总的用户数-->
<select id="findTotal" resultType="int">
    select count(*) from user
</select>

加入聚合查询的测试方法

/**
 *  测试查询总的用户数
 */
@Test
public void testFindTotal(){
    int total = userDao.findTotal();
    System.out.println("总用户数: " + total);
}

2.1.7#{}${}的区别

#{}表示一个占位符号
通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,#{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类型值,#{}括号中可以是 value 或其它名称。
**{}表示拼接 sql 串** 通过{}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, {}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,{}括号中只能是 value。

2.2 mybatis的参数和返回值

2.2.1 parameterType 配置参数

​ 我们在上一章节中已经介绍了 SQL 语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装类,本章节将介绍如何使用实体类的包装类作为参数传递。

​ 基 本 类 型 和 String 我 们 可 以 直 接 写 类 型 名 称 , 也 可 以 使 用 包 名 . 类 名 的 方 式 , 例 如 :java.lang.String。

​ 实体类类型,目前我们只能使用全限定类名。
究其原因,是 mybaits 在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。在今天课程的最后一个章节中将讲解如何注册实体类
的别名。

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

2.2.2 传递 pojo 包装对象

​ 开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
Pojo 类中包含 pojo。
需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。

  1. 编写 QueryVo
/**
 * 查询条件对象
 */
public class QueryVo implements Serializable {

    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}
  1. 编写持久层接口
/**
 * 根据QueryVo中的条件查询用户
 * @param vo
 * @return
 */
List<User> findByVo(QueryVo vo); 
  1. 持久层接口的映射文件

    <!--根据QueryVo中的条件查询用户-->
    <select id="findByVo" parameterType="org.example.domain.QueryVo" resultType="org.example.domain.User">
        select * 
        from user 
        where username like concat('%',#{user.username},'%')
    </select>
    
    1. 测试包装类作为参数
    /**
     * 测试根据QueryVo中的条件查询用户
     */
    @Test
    public void testFindByVo(){
        QueryVo vo = new QueryVo();
        User user = new User();
        user.setUsername("王");
        vo.setUser(user);
        List<User> list = userDao.findByVo(vo);
        for (User u: list) {
            System.out.println(u);
        }
    }
    

2.2.3 resultType 配置结果类型

​ resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。
我们在前面的 CRUD 案例中已经对此属性进行过应用了。
需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须
使用全限定类名。例如:我们的实体类此时必须是全限定类名(今天最后一个章节会讲解如何配置实体类的别名)
同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。

2.2.3.1 基本类型示例

  1. Dao 接口
// 查询总记录条数
int findTotal();
  1. 映射配置

  2. <!-- 查询总记录条数 -->
    <select id="findTotal" resultType="int">
      select count(*) from user;
    </select>
    

2.2.3.2 实体类类型示例

  1. Dao 接口
/**
 * 查询所有用户操作
 * @return
 */
List<User> findAll();
  1. 映射配置
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="org.example.domain.User">
    select * from user
</select>

2.2.4 resultMap 结果类型

resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。
select 标签中使用 resultMap 属性指定引用即可。同时 resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括 pojolist 实现一对一查询和一对多查询。

1.定义 resultMap

<!-- 建立 User 实体和数据库表的对应关系
    type 属性:指定实体类的全限定类名
    id 属性:给定一个唯一标识,是给查询 select 标签引用用的。
-->
<resultMap type="org.example.domain.User" id="userMap">
    <id column="id" property="userId"/>
    <result column="username" property="userName"/>
    <result column="sex" property="userSex"/>
    <result column="address" property="userAddress"/>
    <result column="birthday" property="userBirthday"/>
</resultMap>
id 标签:用于指定主键字段
result 标签:用于指定非主键字段
column 属性:用于指定数据库列名
property 属性:用于指定实体类属性名称 
  1. 映射配置

    <select id="findAll" resultMap="userMap">
     select * from user
    </select>    
    

2.3 SqlMapConfig.xml配置文件

2.3.1 配置内容

-properties(属性)
    --property
-settings(全局配置参数)
    --setting
-typeAliases(类型别名)
    --typeAliase
    --package
-typeHandlers(类型处理器)
-objectFactory(对象工厂)
-plugins(插件)
-environments(环境集合属性对象)
    --environment(环境子属性对象)
        ---transactionManager(事务管理)
        ---dataSource(数据源)
-mappers(映射器)
    --mapper
    --package

2.3.2 properties(属性)

​ 在使用 properties 标签配置时,我们可以采用两种方式指定属性配置。

第一种

<dataSource type="POOLED">
    <!-- 配置连接数据库的4个基本信息-->
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/easy?useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</dataSource>

第二种

  1. classpath 下定义 db.properties文件

    properties 标签设置 resource="jdbcConfig.properties" 可以直接使用

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234

<properties resource="jdbcConfig.properties">
</properties>

<environments default="mysql">
        <!--配置mysql的环境-->
        <environment id="mysql">
            <!-- 配置事务类型-->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!-- 配置连接数据库的4个基本信息-->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

  1. properties标签配置
<!-- 配置连接数据库的信息
resource 属性:用于指定 properties 配置文件的位置,要求配置文件必须在类路径下
resource="jdbcConfig.properties"
url 属性:
    URL: Uniform Resource Locator 统一资源定位符
    http://localhost:8080/mystroe/CategoryServlet

URI
    URI:Uniform Resource Identifier 统一资源标识符
    /mystroe/CategoryServlet
    它是可以在 web 应用中唯一定位一个资源的路径
-->

<properties url=file:///D:/IdeaProjects/day02_eesy_01mybatisCRUD/src/main/resources/jdbcConfig.properties">
</properties>
  1. 此时我们的 dataSource 标签就变成了引用上面的配置

    <dataSource type="POOLED">
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </dataSource>
    

2.3.3 mappers(映射器)

  1. <mapper resource=" " />
使用相对于类路径的资源
如:<mapper resource="com/itheima/dao/IUserDao.xml" />
  1. <mapper class=" " />
使用 mapper 接口类路径
如:<mapper class="com.itheima.dao.UserDao"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
  1. <package name=""/>
注册指定包下的所有 mapper 接口
如:<package name="cn.itcast.mybatis.mapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。

第三天:mybatis的深入和多表

3.1 Mybatis 连接池与事务深入

3.1.1 Mybatis 的连接池技术

​ 我们在前面的 WEB 课程中也学习过类似的连接池技术,而在 Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 Mybatis 的 SqlMapConfig.xml 配置文件中,通过<dataSource type=”pooled”>来实现 Mybatis 中连接池的配置。
Mybatis 连接池的分类

3.1.1.1 Mybatis 连接池的分类

在 Mybatis 中我们将它的数据源 dataSource 分为以下几类:

UNPOOLED 不使用连接池的数据源
POOLED 使用连接池的数据源
JNDI 使用 JNDI 实现的数据源

​ 相应地,MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSource,
PooledDataSource 类来表示 UNPOOLED、POOLED 类型的数据源。

​ 在这三种数据源中,我们一般采用的是 POOLED 数据源(很多时候我们所说的数据源就是为了更好的管理数据库连接,也就是我们所说的连接池技术)。

Mybatis 中数据源的配置

3.1.1.2 Mybatis 中数据源的配置

​ 我们的数据源配置就是在 SqlMapConfig.xml文件中,具体配置如下:


<!-- 配置数据源(连接池)信息 -->
<dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

​ MyBatis 在初始化时,根据<dataSource>的 type 属性来创建相应类型的的数据源 DataSource,即:
type="POOLED": MyBatis 会创建 PooledDataSource 实例
type="UNPOOLED" : MyBatis 会创建 UnpooledDataSource 实例

type="JNDI": MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用

3.1.1.3 Mybatis 中 DataSource的存取

​ MyBatis 是 通 过 工 厂 模 式 来 创 建 数 据 源 DataSource 对 象 的 , MyBatis 定 义 了 抽 象 的 工 厂 接口 :org.apache.ibatis.datasource.DataSourceFactory,通过其 getDataSource()方法返回数据源DataSource
下面是 DataSourceFactory 源码,具体如下:

package org.apache.ibatis.datasource;
import java.util.Properties;
import javax.sql.DataSource;
    /**
     * @author Clinton Begin
     */
    public interface DataSourceFactory {
        void setProperties(Properties props);
        DataSource getDataSource();
    }

​ MyBatis 创建了 DataSource 实例后,会将其放到 Configuration 对象内的 Environment对象中, 供以后使用。

具体分析过程如下:

1.先进入 XMLConfigBuilder类中,可以找到如下代码:

image.png

2.分析 configuration对象的 environment属性,结果如下:

image.png

3.1.1.4 Mybatis 中连接的获取过程分析

​ 当我们需要创建 SqlSession 对象并需要执行 SQL 语句时,这时候 MyBatis 才会去调用 dataSource 对象来创建 java.sql.Connection对象。也就是说,java.sql.Connection对象的创建一直延迟到执行 SQL 语句的时候。

@Test
public void testSql() throws Exception {
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    SqlSession sqlSession   = factory.openSession();
    List<User> list = sqlSession.selectList("findUserById",41);
    System.out.println(list.size());
}

​ 只有当第 4 句 sqlSession.selectList("findUserById"),才会触发 MyBatis 在底层执行下面这个方
法来创建 java.sql.Connection对象。
如何证明它的加载过程呢?
我们可以通过断点调试,在 PooledDataSource 中找到如下 popConnection()方法,如下所示:

image.png

分析源代码,得出 PooledDataSource 工作原理如下:

image.png

下面是连接获取的源代码:

image.png

​ 最后我们可以发现,真正连接打开的时间点,只是在我们执行 SQL 语句时,才会进行。其实这样做我们也可以进一步发现,数据库连接是我们最为宝贵的资源,只有在要用到的时候,才去获取并打开连接,当我们用完了就再立即将数据库连接归还到连接池中。

3.1.2 Mybatis 的事务控制

3.1.2.1 Mybatis 中手动提交事务

​ Mybatis 中事务的提交方式,本质上就是调用 JDBC 的 setAutoCommit()来实现事务控制。
我们运行之前所写的代码:

@Test
public void testSaveUser() throws Exception {
    User user = new User();
    user.setUsername("mybatis user09");
    //6.执行操作
    int res = userDao.saveUser(user);
    System.out.println(res);
    System.out.println(user.getId());
}
@Before//在测试方法执行之前执行
public void init()throws Exception {
    //1.读取配置文件
    in = Resources.getResourceAsStream("SqlMapConfig.xml");
    //2.创建构建者对象
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //3.创建 SqlSession 工厂对象
    factory = builder.build(in);
    //4.创建 SqlSession 对象
    session = factory.openSession();
    //5.创建 Dao 的代理对象
    userDao = session.getMapper(IUserDao.class);
}
@After//在测试方法执行完成之后执行
public void destroy() throws Exception{
    //7.提交事务
    session.commit();
    //8.释放资源
    session.close();
    in.close();
}

​ 这是我们的Connection 的整个变化过程,通过分析我们能够发现之前的 CUD 操作过程中,我们都要手动进行事务的提交,原因是 setAutoCommit()方法,在执行时它的值被设置为 false 了,所以我们在 CUD 操作中,必须通过 sqlSession.commit()方法来执行提交操作

3.1.2.2 Mybatis 自动提交事务的设置

​ 通过上面的研究和分析,现在我们一起思考,为什么 CUD 过程中必须使用 sqlSession.commit()提交事务?主要原因就是在连接池中取出的连接,都会将调用 connection.setAutoCommit(false)方法,这样我们就必须使用 sqlSession.commit()方法,相当于使用了 JDBC 中的 connection.commit()方法实现事务提交。
明白这一点后,我们现在一起尝试不进行手动提交,一样实现 CUD 操作。

@Before//在测试方法执行之前执行
public void init()throws Exception {
    //1.读取配置文件
    in = Resources.getResourceAsStream("SqlMapConfig.xml");
    //2.创建构建者对象
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //3.创建 SqlSession 工厂对象
    factory = builder.build(in);
    //4.创建 SqlSession 对象
    session = factory.openSession(true);
    //5.创建 Dao 的代理对象
    userDao = session.getMapper(IUserDao.class);
}
@After//在测试方法执行完成之后执行
public void destroy() throws Exception{
    //7.释放资源
    session.close();
    in.close();
}

​ 我们发现,此时事务就设置为自动提交了,同样可以实现 CUD 操作时记录的保存。虽然这也是一种方式,但就编程而言,设置为自动提交方式为 false 再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务情况来决定提交是否进行提交。

3.2 Mybatis 的动态 SQL 语句

3.2.1 动态 SQL 之<if>标签

​ 我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询,如果 username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

  1. 持久层 Dao 接口
/**
 * 根据传入参数条件
 * @param user 查询的条件,有可能有用户名,有可能有性别,也有可能有地址,还有可能是都有
 * @return
 */
List<User> findUserByCondition(User user);
  1. 持久层 Dao 映射配置
<!--根据条件查询-->
<select id="findUserByCondition" resultType="org.example.domain.User" parameterType="org.example.domain.User">
    select * from user
    where 1=1
    <if test="username != null and username != '' ">
        and username like concat('%',#{username},'%')
    </if>
    <if test="address != null and address != '' ">
        and address like concat('%',#{address},'%')
    </if>
</select>

​ 注意:<if>标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。
另外要注意 where 1=1 的作用~!

  1. 测试

    @Test
    public void  testFindUserByCondition(){
        User user = new User();
        user.setUsername("王");
        List<User> list = userDao.findUserByCondition(user);
        for (User u: list) {
            System.out.println(u);
        }
    }
    

3.2.2 动态 SQL 之<where>标签

为了简化上面 where 1=1 的条件拼装,我们可以采用<where>标签来简化开发。

持久层 Dao 映射配置

<!--根据条件查询-->
<select id="findUserByCondition" resultType="org.example.domain.User" parameterType="org.example.domain.User">
    select * from user
    <where>
        <if test="username != null and username != '' ">
            and username like concat('%',#{username},'%')
        </if>
        <if test="address != null and address != '' ">
            and address like concat('%',#{address},'%')
        </if>
    </where>
</select>

3.2.3 动态标签之<foreach>标签

​ 传入多个 id 查询用户信息,用下边两个 sql 实现:
SELECT * FROM USERS WHERE username LIKE '%张%' AND (id =10 OR id =89 OR id=16)
SELECT * FROM USERS WHERE username LIKE '%张%' AND id IN (10,89,16)

​ 这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。
这样我们将如何进行参数的传递?

  1. 在 QueryVo 中加入一个 List 集合用于封装参数
public class QueryVo implements Serializable {

    private User user;

    private List<Integer> ids;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public List<Integer> getIds() {
        return ids;
    }

    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }
}
  1. 持久层 Dao 接口

    /**
     * 根据queryVo提供的id集合,查询用户信息
     * @param vo
     * @return
     */
    List<User> findUserInIds(QueryVo vo);
    
  2. 持久层 Dao 映射配置

<!--根据queryVo提供的id集合,查询用户信息-->
<select id="findUserInIds" resultType="org.example.domain.User" parameterType="org.example.domain.QueryVo">
    select * from user
    <where>
        <if test="ids != null and ids.size() > 0">
            <foreach collection="ids" open="id in (" close=")" item="id" separator=",">
                #{id}
            </foreach>
        </if>
    </where>

</select>

SQL 语句:
select 字段 from user where id in (?)
<foreach> 标签用于遍历集合,它的属性:
collection: 代表要遍历的集合元素,注意编写时不要写#{}
open: 代表语句的开始部分
close: 代表结束部分
item="id" item: 代表遍历集合的每个元素,生成的变量名
sperator: 代表分隔符

  1. 编写测试方法

    /**
     * foreach 标签的使用
     */
    @Test
    public void  testFindUserInIds(){
        QueryVo vo = new QueryVo();
        List<Integer> ids = new ArrayList<Integer>();
        ids.add(41);
        ids.add(42);
        ids.add(51);
        vo.setIds(ids);
    
        List<User> list = userDao.findUserInIds(vo);
        for (User u: list) {
            System.out.println(u);
        }
    }
    

3.2.4 Mybatis 中简化编写的 SQL 片段

​ Sql 中可将重复的 sql提取出来,使用时用 include引用即可,最终达到 sql 重用的目的。

  1. 定义代码片段

    <sql id="defaultUser">
        select * from user
    </sql>
    
  1. 引用代码片段

        <!-- 配置查询所有操作 -->
        <select id="findAll" resultType="org.example.domain.User">
            <include refid="defaultUser"></include>
    <!--        select * from user-->
        </select>
    

3.3 Mybatis 多表查询之一对多

3.3.1 一对一查询(多对一)

​ 使用 resultMap,定义专门的 resultMap 用于映射一对一查询结果。
通过面向对象的(has a)关系可以得知,我们可以在 Account 类中加入一个 User 类的对象来代表这个账户是哪个用户的。

1. 定义账户信息的实体类

/**
 * 账号实体类
 */
public class Account implements Serializable {

    private  Integer id;

    private Integer uid;

    private Double money;

    //从表实体应该包含一个主体实体的对象引用
    private User user;


    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                ", user=" + user +
                '}';
    }
}

2. 编写 Sql 语句

select b.*,a.id as aid,a.uid,a.money from  account a, user b where a.uid = b.id;

3. 定义账户的持久层 Dao 接口

/**
 * 查询所有账号,同时还要获取到当前账号的所属用户信息
 * @return
 */
List<Account> findAll();

4. 定义 AccountDao.xml 文件中的查询配置信息

<!-- 定义account和user的resultMap-->
    <resultMap id="accountUserMap" type="org.example.domain.Account">
        <id property="id" column="aid"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>

        <!-- 一对一的关系映射: 配置封装user的内容 -->
        <association property="user" column="uid" javaType="org.example.domain.User">
            <id property="id" column="id"></id>
            <result property="username" column="username"></result>
            <result property="address" column="address"></result>
            <result property="sex" column="sex"></result>
            <result property="birthday" column="birthday"></result>
        </association>
    </resultMap>

    <!-- 查询所有账号-->
    <select id="findAll" resultMap="accountUserMap">
         select b.*,a.id as aid,a.uid,a.money from  account a, user b where a.uid = b.id;

    </select>

5. 测试方法

/**
 * 查询所有账号
 */
@Test
public void testFindAll()  {
    //6.使用代理对象执行查询所有方法
    List<Account> accounts = accountDao.findAll();
    for(Account account : accounts) {
        System.out.println(account);
        //System.out.println(account.getUser());
    }
}

3.3.2 一对多查询

​ 需求:
查询所有用户信息及用户关联的账户信息。
分析:
用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息
查询出来,我们想到了左外连接查询比较合适。

1 . 编写 SQL 语句

SELECT
 u.*,
 a.ID as aid,
 a.MONEY
FROM
 user u
LEFT JOIN account a on
 u.id = a.UID

2 . User 类加入 List<Account>

/**
 * 用户的实体类
 */
public class User implements Serializable {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    // 一对多关系映射; 主表实体应该包含从表实体的集合引用
    private List<Account> accounts;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

3. 用户持久层 Dao 接口中加入查询方法

/**
 * 查询所有用户操作
 * @return
 */
List<User> findAll();

4. 用户持久层 Dao 映射文件配置

<!-- 定义User的resultMap -->
    <resultMap id="userAccountMap" type="org.example.domain.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
        <result property="address" column="address"></result>
        <!--配置user对象中国accounts集合映射-->
        <collection property="accounts" ofType="org.example.domain.Account">
            <id property="id" column="aid"></id>
            <result property="uid" column="uid"></result>
            <result property="money" column="money"></result>
        </collection>

    </resultMap>

    <!-- 配置查询所有操作 -->
    <select id="findAll" resultMap="userAccountMap">
        SELECT
            u.*,
            a.ID as aid,
            a.MONEY
        FROM
            user u
        LEFT JOIN account a on
            u.id = a.UID    
    </select>

5. 测试方法

@Test
    public void testFindAll()  {
        //6.使用代理对象执行查询所有方法
        List<User> users = userDao.findAll();
        for(User user : users) {
            System.out.println("---------------------------------");
            System.out.println(user);
            for (Account account : user.getAccounts()){
                System.out.println(account);
            }
        }
    }

3.4 Mybatis 多表查询之多对多

3.4.1 实现 Role 到 User 多对多

通过前面的学习,我们使用 Mybatis 实现一对多关系的维护。多对多关系其实我们看成是双向的一对多关系。

1 业务要求及实现 SQL需求:

实现查询所有对象并且加载它所分配的用户信息。
    分析:
    查询角色我们需要用到 Role 表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE 表)才能关联到用户信息。
   下面是实现的 SQL 语句:
SELECT
 r.id as rid,
 r.role_name,
 r.role_desc,
 u.id ,
 u.username,
 u.birthday,
 u.sex,
 u.address
FROM
`role` r
LEFT JOIN user_role ur ON
r.ID = ur.RID
LEFT JOIN `user` u ON u.id = ur.UID

2 编写角色实体类

public class Role  implements Serializable {

    private Integer roleId;

    private String roleName;

    private String roleDesc;

    // 多对多的关系映射: 一个角色可以赋予多个用户
    private List<User> users;

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    public Integer getRoleId() {
        return roleId;
    }

    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getRoleDesc() {
        return roleDesc;
    }

    public void setRoleDesc(String roleDesc) {
        this.roleDesc = roleDesc;
    }

    @Override
    public String toString() {
        return "Role{" +
                "roleId=" + roleId +
                ", roleName='" + roleName + '\'' +
                ", roleDesc='" + roleDesc + '\'' +
                '}';
    }
}

3 编写 Role 持久层接口

    /**
     * 查询所有角色
     * @return
     */
    List<Role> findAll();
}

4 编写映射文件

<!--定义Role表的ResultMap-->
<resultMap id="roleMap" type="org.example.domain.Role">
    <id property="roleId" column="rid"></id>
    <result property="roleName" column="ROLE_NAME"></result>
    <result property="roleDesc" column="ROLE_DESC"></result>
    <collection property="users" ofType="org.example.domain.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>

    </collection>
</resultMap>

<!-- 查询所有角色-->
<select id="findAll" resultMap="roleMap">
    SELECT
        r.id as rid,
        r.role_name,
        r.role_desc,
        u.id ,
        u.username,
        u.birthday,
        u.sex,
        u.address
    FROM
    `role` r
    LEFT JOIN user_role ur ON
    r.ID = ur.RID
    LEFT JOIN `user` u ON u.id = ur.UID
</select>

5 编写测试类

public class RoleTest {

    private  InputStream in;
    private  SqlSession sqlSession;
    private RoleDao roleDao;

    @Before  // test方法执行之前执行
    public void init() throws IOException {
            //1.读取配置文件
             in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建 SqlSessionFactory 的构建者对象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

            //3.使用构建者创建工厂对象 SqlSessionFactory
            SqlSessionFactory factory = builder.build(in);

            //4.使用 SqlSessionFactory 生产 SqlSession 对象
            sqlSession = factory.openSession();

            //5.使用 SqlSession 创建 dao 接口的代理对象
        roleDao = sqlSession.getMapper(RoleDao.class);

    }

    @After // test方法执行之后执行
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //7.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 查询所有账号
     */
    @Test
    public void testFindAll()  {
        //6.使用代理对象执行查询所有方法
        List<Role> roles = roleDao.findAll();
        for(Role role : roles) {
            System.out.println(role);
            System.out.println(role.getUsers());
        }
    }
}

12

3.4.2 实现 User 到 Role 的多对多

​ 从 User 出发,我们也可以发现一个用户可以具有多个角色,这样用户到角色的关系也还是一对多关系。这样我们就可以认为 User 与 Role 的多对多关系,可以被拆解成两个一对多关系来实现。

1 业务要求及实现 SQL需求:

SELECT
    u.id ,
    u.username,
    u.birthday,
    u.sex,
    u.address,
    r.id as rid,
    r.role_name,
    r.role_desc 
FROM
`user` u
LEFT JOIN user_role ur ON
u.ID = ur.UID
LEFT JOIN `role` r ON r.id = ur.RID

2 编写角色实体类

/**
 * 用户的实体类
 */
public class User implements Serializable {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    // 一对多关系映射; 主表实体应该包含从表实体的集合引用
    private List<Account> accounts;
    // 多对多的关系映射: 一个用户可以具有多个角色
    private List<Role> roles;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

3 编写 Role 持久层接口

/**
 * 查询所有用户及权限信息
 * @return
 */
List<User> findAllRole();

4 编写映射文件

<!-- 定义User权限的resultMap -->
<resultMap id="userRoleMap" type="org.example.domain.User">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="sex" column="sex"></result>
    <result property="birthday" column="birthday"></result>
    <result property="address" column="address"></result>
    <!--配置user对象中国accounts集合映射-->
    <collection property="roles" ofType="org.example.domain.Role">
        <id property="roleId" column="rid"></id>
        <result property="roleName" column="role_name"></result>
        <result property="roleDesc" column="role_desc"></result>
    </collection>

</resultMap>

<select id="findAllRole" resultMap="userRoleMap">
        SELECT
            u.id ,
            u.username,
            u.birthday,
            u.sex,
            u.address,
            r.id as rid,
            r.role_name,
            r.role_desc
        FROM
        `user` u
        LEFT JOIN user_role ur ON
        u.ID = ur.UID
        LEFT JOIN `role` r ON r.id = ur.RID
    </select>

5 编写测试类

@Test
public void testFindAllRole(){
    List<User> users = userDao.findAllRole();
    for (User user : users){
        System.out.println(user);
        System.out.println(user.getRoles());
    }
}

第四天:mybatis的缓存和注解开发

4.1 Mybatis 延迟加载策略(查询的时机)

​ 通过前面的学习,我们已经掌握了 Mybatis 中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。

4.1.1 为什么要用延迟加载策略

延迟加载:
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
好处:

​ 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处:
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

实现需求

需求:
查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可满足要求,当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载。
mybatis 第三天实现多表操作时,我们使用了 resultMap 来实现一对一,一对多,多对多关系的操作。主要是通过 associationcollection 实现一对一及一对多映射。associationcollection 具备延迟加载功能。

实际使用场景

在对应的四种表关系中:一对多,一对一,多对多
    一对多,多对多:通常情况下我们都是采用延迟加载。
    一对一:通常情况下我们都是采用立即加载。

4.1.2 使用 assocation 实现延迟加载(一对一)

​ 需求:
查询账户信息同时查询用户信息。

1. 编写账户实体类

/**
 * 账号实体类
 */
public class Account implements Serializable {

    private  Integer id;

    private Integer uid;

    private Double money;

    //从表实体应该包含一个主体实体的对象引用
    private User user;

// getter + setter 省略

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                '}';
    }
}

2.账户的持久层 DAO 接口

public interface AccountDao {

    /**
     * 查询所有账号,同时还要获取到当前账号的所属用户信息
     * @return
     */
    List<Account> findAll();
}

3. 账户的持久层映射文件

    <!-- 定义account和user的resultMap-->
    <resultMap id="accountUserMap" type="org.example.domain.Account">
        <id property="id" column="id"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!-- 一对一的关系映射: 配置封装user的内容  配置延迟加载-->
        <association property="user" column="uid" javaType="org.example.domain.User" select="org.example.dao.UserDao.findById">
        </association>

    </resultMap>

    <!-- 查询所有账号-->
    <select id="findAll" resultMap="accountUserMap">
            select * from account
    </select>

select: 填写我们要调用的 select 映射的 id
column : 填写我们要传递给 select 映射的参数

4. 用户的持久层接口和映射文件

/**
 * 根据Id查询用户
 */
User findById(Integer id);

    <!-- 根据Id查询用户-->
    <select id="findById" parameterType="java.lang.Integer" resultType="org.example.domain.User">
    select * from  user where id = #{id}
    </select>

5. 编写测试只查账户信息不查用户信息。

public class AccountTest {

    private  InputStream in;
    private  SqlSession sqlSession;
    private AccountDao accountDao;

    @Before  // test方法执行之前执行
    public void init() throws IOException {
            //1.读取配置文件
             in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建 SqlSessionFactory 的构建者对象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

            //3.使用构建者创建工厂对象 SqlSessionFactory
            SqlSessionFactory factory = builder.build(in);

            //4.使用 SqlSessionFactory 生产 SqlSession 对象
            sqlSession = factory.openSession();

            //5.使用 SqlSession 创建 dao 接口的代理对象
            accountDao = sqlSession.getMapper(AccountDao.class);

    }

    @After // test方法执行之后执行
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //7.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 查询所有账号
     */
    @Test
    public void testFindAll()  {
        List<Account> accounts = accountDao.findAll();
        for(Account account : accounts) {
           // System.out.println(account);
        }
    }
 
}

6 测试结果

DEBUG example.dao.AccountDao.findAll  - ==>  Preparing: select * from account 
DEBUG example.dao.AccountDao.findAll  - ==> Parameters: 
DEBUG example.dao.AccountDao.findAll  - <==      Total: 3
DEBUG ansaction.jdbc.JdbcTransaction  - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@612679d6]
DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@612679d6]
DEBUG source.pooled.PooledDataSource  - Returned connection 1629911510 to pool.

​ 我们发现,因为本次只是将 Account 对象查询出来放入 List 集合中,并没有涉及到 User 对象,所以就没有发出 SQL 语句查询账户所关联的 User 对象的查询。

4.1.3 使用 Collection 实现延迟加载(一对多)

同样我们也可以在一对多关系配置的<collection>结点中配置延迟加载策略。
<collection>结点中也有 select 属性,column 属性。
需求:
完成加载用户对象时,查询该用户所拥有的账户信息

1. 在 User 实体类中加入List<Account>属性

/**
 * 用户的实体类
 */
public class User implements Serializable {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    // 一对多关系映射; 主表实体应该包含从表实体的集合引用
    private List<Account> accounts;
    // 多对多的关系映射: 一个用户可以具有多个角色
    private List<Role> roles;

// getter setter 省略

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

2. 编写用户和账户持久层接口的方法

/**
 * 查询所有用户操作
 * @return
 */
List<User> findAll();

/**
 *查询所有账户 根据用户id 
 * @return
 */
List<Account> findAllByUid(Integer uid);

3. 编写用户持久层映射配置

<!-- 定义User账号的resultMap -->
<resultMap id="userMap" type="org.example.domain.User">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="sex" column="sex"></result>
    <result property="birthday" column="birthday"></result>
    <result property="address" column="address"></result>
   
   <!--配置user对象中国accounts集合映射  延迟加载-->
    <collection property="accounts" column="id" ofType="org.example.domain.Account" select="org.example.dao.AccountDao.findAllByUid">

    </collection>

</resultMap>

<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
    select * from user
</select>

<collection>标签:
        主要用于加载关联的集合对象
select 属性:
        用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id
column 属性:
        用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一个字段名了

4. 编写账户持久层映射配置

<select id="findAllByUid" resultType="org.example.domain.Account">
    select * from account where uid= #{uid}
</select>

5. 测试只加载用户信息

 package org.example.test;


import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.example.dao.UserDao;
import org.example.domain.Account;
import org.example.domain.QueryVo;
import org.example.domain.User;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import sun.nio.ch.Net;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 *  mybatis 入门案例
 */
public class UserTest {

    private  InputStream in;
    private  SqlSession sqlSession;
    private  UserDao userDao;

    @Before  // test方法执行之前执行
    public void init() throws IOException {
            //1.读取配置文件
             in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建 SqlSessionFactory 的构建者对象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

            //3.使用构建者创建工厂对象 SqlSessionFactory
            SqlSessionFactory factory = builder.build(in);

            //4.使用 SqlSessionFactory 生产 SqlSession 对象
            sqlSession = factory.openSession();

            //5.使用 SqlSession 创建 dao 接口的代理对象
            userDao = sqlSession.getMapper(UserDao.class);

    }

    @After // test方法执行之后执行
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //7.释放资源
        sqlSession.close();
        in.close();
    }


    @Test
    public void testFindAll()  {
        //6.使用代理对象执行查询所有方法
        List<User> users = userDao.findAll();
    }

}

6 测试结果:

测试结果如下:

DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
DEBUG source.pooled.PooledDataSource  - Created connection 1629911510.
DEBUG ansaction.jdbc.JdbcTransaction  - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@612679d6]
DEBUG rg.example.dao.UserDao.findAll  - ==>  Preparing: select * from user 
DEBUG rg.example.dao.UserDao.findAll  - ==> Parameters: 
DEBUG rg.example.dao.UserDao.findAll  - <==      Total: 10
DEBUG ansaction.jdbc.JdbcTransaction  - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@612679d6]
DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@612679d6]
DEBUG source.pooled.PooledDataSource  - Returned connection 1629911510 to pool.

我们发现并没有加载 Account 账户信息。

4.2 mybatis中的一级缓存和二级缓存

4.2.1 Mybatis 一级缓存

4.2.1.1证明一级缓存的存在

​ 一级缓存是 SqlSession级别的缓存,只要 SqlSession 没有 flushclose,它就存在。

1. 编写用户持久层 Dao 接口
public interface UserDao {
    /**
     * 根据 id 查询
     * @param userId
     * @return
     */
    User findById(Integer userId);
}
2. 编写用户持久层映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IUserDao">
    <!-- 根据 id 查询 -->
    <select id="findById" resultType="UsEr" parameterType="int" useCache="true">
        select * from user where id = #{uid}
    </select>
</mapper>
3. 编写测试方法
@Test
public void testFindById() {
    User user = userDao.findById(41);
    System.out.println("第一次查询的用户:"+user);
    User user2 = userDao.findById(41);
    System.out.println("第二次查询用户:"+user2);
    System.out.println(user == user2);
}
4. 测试结果
DEBUG ansaction.jdbc.JdbcTransaction  - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e720b71]
DEBUG g.example.dao.UserDao.findById  - ==>  Preparing: select * from user where id = ? 
DEBUG g.example.dao.UserDao.findById  - ==> Parameters: 49(Integer)
DEBUG g.example.dao.UserDao.findById  - <==      Total: 1
第一次查询的用户:org.example.domain.User@17d0685f
第二次查询的用户:org.example.domain.User@17d0685f
true

    我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 Mybatis 提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句从数据库中查询数据,而是从一级缓存中查询。

4.2.1.2 一级缓存的分析

​ 一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查
询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果 sqlSession去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。

4.2.2 Mybatis 二级缓存

​ 二级缓存是 mapper 映射级别的缓存,多个 SqlSession去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession的。

​ 首先开启 mybatis 的二级缓存。
sqlSession1 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。
如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 commit 提交,将会清空该 mapper 映射下的二级缓存区域的数据。
sqlSession2 去查询与 sqlSession1 相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

4.2.2.1 二级缓存的开启与关闭

第一步:在 SqlMapConfig.xml文件开启二级缓存
<settings>
    <!-- 开启二级缓存的支持 -->
    <setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。
第二步:配置相关的Mapper 映射文件
<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.UserDao">
    <!--开启user支持二级缓存-->
    <cache/>
</mapper>
第三步:配置statement上面的useCache 属性
<!-- 根据 id 查询 -->
<select id="findById" resultType="org.example.domain.User" parameterType="int" useCache="true">
    select * from user where id = #{uid}
</select>
将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。

4.2.2.2 二级缓存开启的注意事项

​ 当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。

4.3 mybatis的注解开发

​ 这几年来注解开发越来越流行,Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。本次我们先围绕一些基本的 CRUD 来学习,再学习复杂映射关系及延迟加载。

4.3.1 mybatis 的常用注解说明

@Insert:实现新增
@Update: 实现更新
@Delete: 实现删除
@Select: 实现查询
@Result: 实现结果集封装
@Results: 可以与@Result 一起使用,封装多个结果集
@ResultMap: 实现引用@Results 定义的封装
@One: 实现一对一结果集封装
@Many: 实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace: 实现注解二级缓存的使用

4.3.2 使用 Mybatis 注解实现基本 CRUD

​ 单表的 CRUD 操作是最基本的操作,前面我们的学习都是基于 Mybaits 的映射文件来实现的。

1. 编写实体类

public class User implements Serializable {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    // 一对多关系映射; 主表实体应该包含从表实体的集合引用
    private List<Account> accounts;
    // 多对多的关系映射: 一个用户可以具有多个角色
    private List<Role> roles;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

2. 使用注解方式开发持久层接口

public interface IUserDao {

    /**
     * 查询所有用户
     * @return
     */

    @Select("select * from user")
    @Results(id = "userMap",value = {
            @Result(id = true,property = "id",column = "id"),
            @Result(property = "username", column = "username"),
            @Result(property = "sex",column = "sex"),
            @Result(property = "birthday",column = "birthday"),
            @Result(property = "address",column = "address")
    })
    List<User> findAll();

    /**
     * 保存用户信息
     * @param user
     * @return
     */
    @Insert("insert into user(username,sex,birthday,address) values(#{username},#{sex},#{birthday},#{address})")
    int saveUer(User user);

    /**
     * 修改用户信息
     * @param user
     * @return
     */
    @Update("update user set username = #{username},sex = #{sex},birthday=#{birthday},address = #{address} where id = #{id}")
    int updateUser(User user);

    /**
     * 根据ID删除用户
     * @param userId
     * @return
     */
    @Delete("delete from user where id = #{userId} ")
    int deleteUser(Integer userId);

    /**
     * 根据ID查询用户
     * @param userId
     * @return
     */
    @ResultMap("userMap")
    @Select("select * from user where id = #{userId} ")
    User findById(Integer userId);

    /**
     * 根据名称模糊查询用户
     * @param username
     * @return
     */
    @ResultMap("userMap")
    @Select("select * from user where username like concat('%',#{username},'%')")
    List<User> findUserByName(String username);

    /**
     * 查询总用户数
     * @return
     */
    @Select("select count(*) from user")
    int findTotalUser();

}
通过注解方式,我们就不需要再去编写 UserDao.xml 映射文件了。

3. 编写测试方法

public class IUserTest {

    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before  // test方法执行之前执行
    public void init() throws IOException {
        //1.读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建 SqlSessionFactory 的构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

        //3.使用构建者创建工厂对象 SqlSessionFactory
        SqlSessionFactory factory = builder.build(in);

        //4.使用 SqlSessionFactory 生产 SqlSession 对象
        sqlSession = factory.openSession();

        //5.使用 SqlSession 创建 dao 接口的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);

    }

    @After // test方法执行之后执行
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //7.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 查询所有账号
     */
    @Test
    public void testFindAll()  {
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user);
        }

    }

    @Test
    public void testSaveUser(){
        User user = new User();
        user.setUsername("唐老鸭");
        user.setSex("男");
        user.setBirthday(new Date());
        user.setAddress("唐宁街10号");
        int i = userDao.saveUer(user);
        System.out.println("受影响行数-> " + i);
    }

    @Test
    public void testUpdateUser(){
        User user = new User();
        user.setId(54);
        user.setUsername("唐老鸭AAA");
        user.setSex("男");
        user.setBirthday(new Date());
        user.setAddress("唐宁街10号");
        int i = userDao.updateUser(user);
        System.out.println("受影响行数-> " + i);
    }

    @Test
    public void testDeleteUser(){
        int i = userDao.deleteUser(56);
        System.out.println("受影响行数-> " + i);
    }

    @Test
    public  void  testFindById(){
        User user = userDao.findById(57);
        System.out.println(user);
    }

    @Test
    public void testFindByName(){
        List<User> users = userDao.findUserByName("唐");
        for(User user: users){
            System.out.println(user);
        }
    }

    @Test
    public void testFindTotla(){
        int total = userDao.findTotalUser();
        System.out.println("总用户数: " + total);
    }

}

4.3.3 使用注解实现复杂关系映射开发

​ 实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>来实现,在使用注解开发时我们需要借助@Results 注解,@Result 注解,@One 注解,@Many 注解。

@Results 注解
代替的是标签 <resultMap>
该注解中可以使用单个@Result注解,也可以使用@Result集合 @Results({@Result(),@Result()})或@Results(@Result())
@Resutl 注解
代替了 <id>标签和<result>标签
@Result 中 属性介绍:
**id** 是否是主键字段
**column** 数据库的列名
**property** 需要装配的属性名
**one** 需要使用的@One 注解(@Result(one=@One()))

**many** 需要使用的@Many 注解(@Result(many=@many()))
@One 注解(一对一)
代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One 注解属性介绍:
**select**
指定用来多表查询的 sqlmapper
**fetchType** 会覆盖全局的配置参数 lazyLoadingEnabled
使用格式:
@Result(column=" ",property="",one=@One(select=""))
@Many 注解(多对一)
代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType(一般为 ArrayList)但是注解中可以不定义;
使用格式:
@Result(property="",column="",many=@Many(select=""))

4.3.3.1使用注解实现一对一复杂关系映射及延迟加载

1 添加 User 实体类及 Account 实体类
public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    // 一对多关系映射; 主表实体应该包含从表实体的集合引用
    private List<Account> accounts;


    // 省略getter,setter 和 toString 方法
    
    }
    
    public class Account implements Serializable {

    private  Integer id;

    private Integer uid;

    private Double money;

    //从表实体应该包含一个主体实体的对象引用
    private User user;
    
    // 省略getter,setter 和 toString 方法
}
    
2. 添加账户的持久层接口并使用注解配置
public interface IAccountDao {

    /**
     * 查询所有账号,并且获取每个账号的用户信息
     * @return
     */
    @Select("select * from account")
    @Results(id = "accountMap",value = {
            @Result(id = true,column = "id",property = "id"),
            @Result(property = "uid",column = "uid"),
            @Result(property = "money",column = "money"),
            @Result(property = "user",column = "uid",one = @One(select = "org.example.dao.IUserDao.findById",fetchType = FetchType.LAZY))
    })
    List<Account> findAll();
}
@One:
相当于<assocation>的配置
    select 属性:代表将要执行的 sql 语句
    fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值
3 添加用户的持久层接口并使用注解配置
public interface IUserDao {

    /**
     * 查询所有用户
     * @return
     */

    @Select("select * from user")
    @Results(id = "userMap",value = {
            @Result(id = true,property = "id",column = "id"),
            @Result(property = "username", column = "username"),
            @Result(property = "sex",column = "sex"),
            @Result(property = "birthday",column = "birthday"),
            @Result(property = "address",column = "address"),
            @Result(property = "accounts",column = "id",many = @Many(select = "org.example.dao.IAccountDao.findAccountByUid",fetchType = FetchType.LAZY))
    })
    List<User> findAll();
    
        /**
     * 根据ID查询用户
     * @param userId
     * @return
     */
    @Select("select * from user where id = #{userId} ")
    User findById(Integer userId);
}
@Many:
相当于<collection>的配置
    select 属性:代表将要执行的 sql 语句
    fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值

4 测试一对一关联及延迟加载
public class IAccountTest {

    private  InputStream in;
    private  SqlSession sqlSession;
    private IAccountDao accountDao;

    @Before  // test方法执行之前执行
    public void init() throws IOException {
            //1.读取配置文件
             in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建 SqlSessionFactory 的构建者对象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

            //3.使用构建者创建工厂对象 SqlSessionFactory
            SqlSessionFactory factory = builder.build(in);

            //4.使用 SqlSessionFactory 生产 SqlSession 对象
            sqlSession = factory.openSession();

            //5.使用 SqlSession 创建 dao 接口的代理对象
            accountDao = sqlSession.getMapper(IAccountDao.class);

    }

    @After // test方法执行之后执行
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //7.释放资源
        sqlSession.close();
        in.close();
    }

    @Test
    public void testFindAll(){
        List<Account> accounts = accountDao.findAll();
        for(Account account : accounts){
            System.out.println("--------每个账号的信息--------");
            System.out.println(account);
            System.out.println("--------每个账号所属人员的信息--------");
            System.out.println(account.getUser());
        }
    }
}

4.3.3.2 使用注解实现一对多复杂关系映射

1 User 实体类加入List<Account>
/**
  * 用户实体类
  */
public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    // 一对多关系映射; 主表实体应该包含从表实体的集合引用
    private List<Account> accounts;


    // 省略getter,setter 和 toString 方法
    
    }
    
/**
  * 账户实体类
  */
    public class Account implements Serializable {

    private  Integer id;

    private Integer uid;

    private Double money;

    //从表实体应该包含一个主体实体的对象引用
    private User user;
    
    // 省略getter,setter 和 toString 方法
}
2 编写用户的持久层接口并使用注解配置
public interface IUserDao {

    /**
     * 查询所有用户
     * @return
     */

    @Select("select * from user")
    @Results(id = "userMap",value = {
            @Result(id = true,property = "id",column = "id"),
            @Result(property = "username", column = "username"),
            @Result(property = "sex",column = "sex"),
            @Result(property = "birthday",column = "birthday"),
            @Result(property = "address",column = "address"),
            @Result(property = "accounts",column = "id",many = @Many(select = "org.example.dao.IAccountDao.findAccountByUid",fetchType = FetchType.LAZY))
    })
    List<User> findAll();
}
@Many:
相当于<collection>的配置
    select 属性:代表将要执行的 sql 语句
    fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值
3 编写账户的持久层接口并使用注解配置
public interface IAccountDao {
    @Select("select * from account where uid = #{userId}")
    List<Account> findAccountByUid(Integer userId);
}
4 添加测试方法
public class IUserTest {

    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before  // test方法执行之前执行
    public void init() throws IOException {
        //1.读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建 SqlSessionFactory 的构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

        //3.使用构建者创建工厂对象 SqlSessionFactory
        SqlSessionFactory factory = builder.build(in);

        //4.使用 SqlSessionFactory 生产 SqlSession 对象
        sqlSession = factory.openSession();

        //5.使用 SqlSession 创建 dao 接口的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);

    }

    @After // test方法执行之后执行
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //7.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 查询所有账号
     */
    @Test
    public void testFindAll()  {
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user);
            for(Account account : user.getAccounts()){
                System.out.println(account);
            }
        }

    }
}

4.3.4 mybatis 基于注解的二级缓存

1 在 SqlMapConfig中开启二级缓存支持

<!-- 配置二级缓存 -->
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>

2 在持久层接口中使用注解配置二级缓存

/**
 * 用户的持久层接口
 */
@CacheNamespace(blocking=true)  //mybatis 基于注解方式实现配置二级缓存
public interface IUserDao {}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容