MyBatis相关概念回顾

本质就是对JDBC底层代码的封装。使用mybatis一般是两种方式,一种是XML,一种是注解。
ORM:关系-对象映射,操作实体类就相当于操作表

Mybatis持久层框架使用 - 图2image.gif

虽然是底层用到了反射,但是真正的原因是,当我们编写User实体的时候,要保证实体的字段名要和数据库的字段名要保持一致。他们保持一致后,执行完sql,拿到sql查询结果之后,则会根据id字段名,去找到User实体中,id这个属性。然后把查询的值赋过去,以此类推。(User实体的字段名和User表的字段名保持一致,就是一种映射关系,底层也是基于这种映射关系来完成结果集的封装)

注:个人理解: 根据底层源码而知,前提是属性名和字段名保持一致,当sql执行后得到结果集返回,就会对结果集进行遍历(也就是预处理对象集合进行遍历),遍历出每一个预处理对象对应的属性名和value值,因为字段名和属性名是一致的,那么此时就可以通过反射技术:根据返回值类型,查询出的字段名,通过反射里面的getDec。。。先得到字段的属性对象,开启对属性的暴力访问,然后对属性进行赋值,并封装到返回类型的对象中。

Mybatis持久层框架使用 - 图4image.gif

半自动:在mybatis中还需要手动编写sql,这样可以能够对sql一步优化。优化后的sql执行效率更高
轻量级:框架在启动的过程中,需要的资源比较少。
优势:MyBatis是一个半自动化的持久化层框架。对开发人员而言, 核心sql还是需要自己优化, sql和java编码分开,功能边界清晰,一个专注业务、一个专注数据。

MyBatis开发步骤:

①添加MyBatis的坐标,引入依赖

②创建user数据表

③编写User实体类(要和表的字段属性要对应)

④编写映射文件UserMapper.xml(在工程的resource文件夹下)

⑤编写核心文件SqlMapConfig.xml

⑥编写测试类

1、新建一个maven项目。
Mybatis持久层框架使用 - 图6image.gif

2、添加jar依赖

  1. <properties>
  2. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  3. <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
  4. <java.version>1.8</java.version>
  5. <maven.compiler.source>1.8</maven.compiler.source>
  6. <maven.compiler.target>1.8</maven.compiler.target>
  7. </properties>
  8. <!--引入依赖-->
  9. <dependencies>
  10. <!--mybatis坐标-->
  11. <dependency>
  12. <groupId>org.mybatis</groupId>
  13. <artifactId>mybatis</artifactId>
  14. <version>3.4.5</version>
  15. </dependency>
  16. <!--mysql驱动坐标-->
  17. <dependency>
  18. <groupId>mysql</groupId>
  19. <artifactId>mysql-connector-java</artifactId>
  20. <version>5.1.6</version>
  21. <scope>runtime</scope>
  22. </dependency>
  23. <!--单元测试坐标-->
  24. <dependency>
  25. <groupId>junit</groupId>
  26. <artifactId>junit</artifactId>
  27. <version>4.12</version>
  28. </dependency>
  29. <!--⽇志坐标-->
  30. <dependency>
  31. <groupId>log4j</groupId>
  32. <artifactId>log4j</artifactId>
  33. <version>1.2.12</version>
  34. </dependency>
  35. </dependencies>

3、添加一个实体类,创建一个与实体类字段一样的表。
(因为只有当字段和属性名一致时,底层才可以通过反射给属性进行一一对应的映射赋值封装)
Mybatis持久层框架使用 - 图8image.gif

Mybatis持久层框架使用 - 图10image.gif

4、添加sql的映射文件
Mybatis持久层框架使用 - 图12image.gif

5、添加数据库的映射文件
Mybatis持久层框架使用 - 图14image.gif

到此,已经完成了1-5,接下来就是第6步。

6、编写测试类。
Mybatis持久层框架使用 - 图16image.gif

Mybatis持久层框架使用 - 图18image.gif
到此,入门案例已经OK。

MyBatis的CRUD回顾

对于mybatis的增删改查都是写在配置文件中的,下面是具体配置的过程

1、要有约束头

2、要有mapper标签

3、要有执行语句的标签 (标签里面有id,参数类型,返回类型,sql语句)

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <!--namespace : 名称空间:与id组成sql的唯一标识
  6. resultType: 表明返回值类型-->
  7. <mapper namespace="user">
  8. <!---查询用户-->
  9. <select id="findAll" resultType="com.shanglin.pojo.User" >
  10. select * from User
  11. </select>
  12. <!--添加用户-->
  13. <!--parameterType:参数类型
  14. (添加操作,那么里面要填什么值,需要传递参数过来) 传递过来的参数就是一个user对象
  15. 那么#{}里面的值,应该与传递过来的类的属性名保持一致,这样就可用其属性的get方法取到值-->
  16. <insert id="saveUser" parameterType="com.shanglin.pojo.User" >
  17. insert into user values(#{id},#{username})
  18. </insert>
  19. <!--修改 需要参数的 获取到相应的属性值给占位符赋值-->
  20. <update id="updateUser" parameterType="com.shanglin.pojo.User">
  21. update user set username = #{username} where id = #{id}
  22. </update>
  23. <!--删除需要参数的 因为删除只需要传递一个id,所以只要添加一个相应的类型
  24. 如果参数类型是基本数据类型,或者是基本数据类型的包装类,且只是一个的时候,
  25. #{}内的值随便写的-->
  26. <delete id="deleteUser" parameterType="java.lang.Integer">
  27. delete from user where id = #{abc}
  28. </delete>
  29. </mapper>

image.gif4、编写测试类

@Test
    public void test() throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
        // 1、查询操作
        List<User> objects = sqlSession.selectList("user.findAll");
        // 设置实体对象传递的参数
        User user = new User();
        user.setId(3);
        user.setUsername("tom");
        // 2、增加
        sqlSession.insert("user.saveUser",user);
        // 3、修改
        sqlSession.update("user.updateUser",user);
        // 4、删除 只传id
        sqlSession.update("user.deleteUser",3);
        // 需要调用提交事物,否则不会入库
        sqlSession.commit();
        sqlSession.close();
    }

image.gif

映射配置文件分析

Mybatis持久层框架使用 - 图22image.gif

核心配置文件分析

Mybatis持久层框架使用 - 图24image.gif

Mybatis持久层框架使用 - 图26image.gif

  //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
  InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
  //2.解析了配置文件,并创建了sqlSessionFactory工厂(工厂的作用是生产SqlSession)
  SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
  //3.生产sqlSession
  // openSession是默认开启一个事务,但是该事务不会自动提交
  //在进行增删改操作时,要手动提交事务
  SqlSession sqlSession = build.openSession();
  // 在sqlSession会话对象中封装了与数据库交互的方法。
  //4.sqlSession调用方法:查询所有selectList  查询单个:selectOne 添加:insert  修改:update 删除:delete
  List<User> objects = sqlSession.selectList("user.findAll");
 // 如果改为自动提交,则在openSession();里面添加一个true

image.gif

Mybatis中Dao层的实现(代理模式)

使用代理开发方式。因为这种方式只需要在持久层添加接口就可以,不需要像传统那样添加实现类
它的原理是在执行过程中,对接口使用JDK动态代理,产生一个代理对象,由这个代理对象去执行操作
但是使用代理对象时,

必须准守以下规范

Mybatis持久层框架使用 - 图29image.gif

具体用法

1、namespace必须是方法所在接口的全限定类名
2、id必须和方法名保持一致
3、返回类型、参数类型,是最终要分装到哪个类是全限定类名

Mybatis持久层框架使用 - 图31image.gif

代理模式getMapper

只需要传递接口,然后通过代理对象调用接口方法

注意:如果不用代理对象,用其他的,则需要填写全路径,eg:如下

Mybatis持久层框架使用 - 图33
image.gif
如果使用代理模式,就需要调用getMapper方法,获取动态代理对象,通过动态代理对象去调用要操作的方法。

//使用代理模式的时候,一定要namespace的名字和接口的路径名一致。否则找不到接口 
@Test
    public void test05() throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
        // 通过getMapper去获取代理对象
        IUserDao mapper = sqlSession.getMapper(IUserDao.class);
        // 通过代理对象去调用接口的方法
        List<User> all = mapper.findAll();
        for (User user : all) {
            System.out.println(user);
        }
        sqlSession.close();
    }

image.gif

Mybatis配置文件的深入

1、properties的配置文件使用

将数据源的配置信息单独的抽取成一个properties文件,然后将这个配置文件加载进到映射文件中进行引用
Mybatis持久层框架使用 - 图36image.gif

Mybatis持久层框架使用 - 图38image.gif

2、typeAliases标签的别名使用

a、单个的别名使用(typeAlias

Mybatis持久层框架使用 - 图40image.gif

b、批量的别名使用(package

批量就直接写实体类的包名即可,因为实体类都在一个包下,而且它不区分大小写嘛。
Mybatis持久层框架使用 - 图42image.gif

Mybatis持久层框架使用 - 图44image.gif

Mybatis持久层框架使用 - 图46image.gif

Mapper.xml的sql配置

根据实体类的不同取值, 使用不同的SQL语句来进行查询,SQL是动态变化的。

if条件一般是用于多条件组合查询中,一般先判读是否为空,若不为空则拼接下去
Mybatis持久层框架使用 - 图48image.gif

1、动态sql——if标签的使用

UserMapper.xml文件中。一定把参数的名字写对咯,不然报错就的debug源码了。

<!--多条件组合查询:演示if-->
    <select id="findByCondition" parameterType="user"  resultType="user">
        select * from User
        <where> <!--会自动拼接where,并去掉第一个and-->
            <if test="id !=null">
                and id = #{id}
            </if>
            <if test="username !=null">
                and username = #{username}
            </if>
        </where>
    </select>

2、动态sql——foreach标签的使用

    <!--多值查询:演示foreach-->
    <select id="findByIds" parameterType="list" resultType="user">
        <!--如:  select * from user where id in (id,id)-->
        select * from user
        <where>
            <!--传过来是数组用array,是集合用List;open是where条件拼接的开始字段;item传递过来的数组正在遍历的值
              用,进行分割-->
            <foreach collection="array" open="id in (" close=")" item="id" separator=",">
                #{id}
            </foreach>
        </where>
    </select>

image.gif
foreach测试代码(采用动态代理模式)

    @Test
    public void test07() throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
        // 通过getMapper去获取代理对象
        IUserDao mapper = sqlSession.getMapper(IUserDao.class);
        int[] arr ={1,2};
        // 通过代理对象去调用接口的方法
        List<User> all = mapper.findByIds(arr);
        for (User user : all) {
            System.out.println(user);
        }
        sqlSession.close();
    }

3、将共用的sql进行抽取出来(抽取sql)

将共用的sql抽取出来,给一个id标识。后续有用到这个sql查询的地方,直接引入id标识即可。
Mybatis持久层框架使用 - 图51image.gif

Mybatis复杂的映射开发

1、一对一查询

a、先创建一个user、order表,一个订单只属于一个用户

Mybatis持久层框架使用 - 图53image.gif

b、然后创建一个项目(进行多表操作)

Mybatis持久层框架使用 - 图55image.gif

c、然后和之前的项目一样,添加依赖。

d、然后创建User,Order实体类(实体类的属性要和表的字段对应 )

e、然后添加数据库的配置文件SqlMapConfig.xml

 需要注意的地方:  1、使用的是mapper这个标签**(不用同包同名,只要放到resource包下就可以)**<br />                                 2、使用的是package这个标签,也就是会加重包下的所有接口。那么就**需要将映射文件 要保**<br />**                                      持和接口所在的包必须同包同名**。就如下图的com.shanglin.mapper<br />![](https://cdn.nlark.com/yuque/0/2020/png/2397310/1598754266897-e1cb568b-5a4d-45b1-90d6-517f914d6a24.png#height=330&id=g95pz&originHeight=330&originWidth=1020&originalType=binary&ratio=1&size=0&status=done&style=none&width=1020)![image.gif](https://cdn.nlark.com/yuque/0/2020/gif/2397310/1598754271555-bf7f7f7e-8128-43dd-8673-558a4c6f8a77.gif#height=1&id=acO0d&name=image.gif&originHeight=1&originWidth=1&originalType=binary&ratio=1&size=43&status=done&style=none&width=1)

具体的配置文件

<!--根标签-->
<configuration>
    <!--加载jdbc.properties的文件信息-->
    <properties resource="jdbc.properties" />
    <typeAliases>
        <!--给单独的实体起别名(适合个数少的时候)-->
      <!--  <typeAlias type="com.shanglin.pojo.User" alias="user"/>-->
        <!--批量起别名:其别名就是该包下所有的类的本身的类名:别名还不区分大小写 (整个包下的所有实体)-->
        <package name="com.shanglin.pojo"/>
    </typeAliases>
    <!--environments:运行环境-->
    <environments default="development">
    <environment id="development">
        <!--当前失误交由JDBCj进行管理-->
        <transactionManager type="JDBC"/>
        <!--当前使用mybatis提供的连接池-->
        <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>
    </environment>
    </environments>
    <mappers>
        <!--这是单个的引入对应的映射配置文件-->
      <!--  <mapper resource="IUserMapper.xml"/>-->
        <!--直接引入某个包下所有的接口对应的映射配置文件
          注意前提是:配置文件和接口要同包同名,所以要创建同名包-->
        <package name="com.shanglin.mapper"/>
        <!--如果是用注解的话,也是需要添加映射的。就是接口的全路径名。
            但是如果是多个的时候,批量的,那还是要上面的导包形式-->
        <!--<mapper class="com.shanglin.mapper.IUserMapper"/>-->
    </mappers>
</configuration>

image.gif

f、然后编写对应的接口,与映射配置文件来实现一对一查询

Mybatis持久层框架使用 - 图58image.gif

Mybatis持久层框架使用 - 图60
image.gif
这样就可以将查询出user的字段这部分,赋给order的user属性上。
Mybatis持久层框架使用 - 图62

按上面的逻辑,使用resultType只能反射获对Order的属性进行封装,获取不能对user的属性进行封装。
所以resultType是不能满足现状的需求,需要改用resultMap来完成。(它可以手动配置封装逻辑)

g、resultMap、association等标签的使用(封装对象)

手动来配置实体属性与表字段的映射关系,的映射配置文件IOrderMapper.xml

//...约束头
<mapper namespace="com.shanglin.mapper.IOrderMapper">
    <cache></cache>
    <!--手动来配置实体属性与表字段的映射关系,它的id属性就是标识,后面直接用其标识即可
        type: 表示当前需要封装到哪个实体对象当中 (当前需要封装到Order中)-->
    <resultMap id="orderMap" type="com.shanglin.pojo.Order">
        <!--这里配置的是Order这个属性,与sql查询出来的结果的映射关系
           property是实体类的属性名,column是数据库表的元素名 -->
        <result property="id" column="id"></result>
        <result property="orderTime" column="orderTime"></result>
        <result property="total" column="total"></result>

        <!--如何把查询出的user信息配置到Order中的user属性中呢?
           首先:user是Order类中的其中一个属性名,表示用来配置Order中user这个属性了
           javaType来表示当前配置这个属性是什么类型,这里是User实体类-->
        <association property="user" javaType="com.shanglin.pojo.User">
            <!--uid,username是用sql查询出的结果的字段,前面是属性的字段-->
            <result property="id" column="uid"></result>
            <result property="username" column="username"></result>
        </association>
    </resultMap>
    <!--这个查询结果,是按照上面id=orderMap的映射关系进行封装,因为调用了上面的标识userMap-->
    <select id="findOrderAndUser" resultMap="orderMap">
        select * from orders o,user u where o.uid=u.id
    </select>
</mapper>

image.gif

2、一对多查询

从用户的角度出发就是一对多(一个用户可以具有多个订单)
一对多的需求:查询所有用户,与此同时查询出该用户所具有的订单信息
那么不能用内连接了,需要用左外或者右外

a、一个用户可以有多个订单,订单用List装起来。

Mybatis持久层框架使用 - 图64image.gif

b、创建接口

public interface IUserMapper {
   public List<TwoUser> findAll();
}

c、resultMap、collection标签的使用(封装集合)

一对多的配置文件

<mapper namespace="com.shanglin.mapper.IUserMapper">
    <cache></cache>
    <!--这里面是一对多的-->
    <!--手动来配置实体属性与表字段的映射关系,它的id属性就是标识,后面直接用其标识即可
           type: 表示当前需要封装到哪个实体对象当中 (当前需要封装到TwoUser中)-->
    <resultMap id="userMap" type="com.shanglin.pojo.TwoUser">
        <result property="id" column="uid"></result>
        <result property="username" column="username"></result>

        <!--这里对应的是多方,多方要用collection
        property:是实体TwoUser中集合的名称orderList,  
        ofType:表示当前集合泛型的一个全路径,表示将查询结果封装成一个个的全路径里面的对象
         (也就是封装成一个个TwoOrder对象)-->
        <collection property="orderList" ofType="com.shanglin.pojo.TwoOrder">
            <!--uid,username是用sql查询出的结果的字段,前面是属性的字段-->
            <result property="id" column="id"></result>
            <result property="orderTime" column="orderTime"></result>
            <result property="total" column="total"></result>
        </collection>
    </resultMap>
    <!--这个查询,是按照上面的映射关系进行封装,因为调用了上面的标识id=userMap-->
    <select id="findAll" resultMap="userMap">
        select * from user u left join orders o on u.id = o.uid
    </select>
</mapper>

d、测试代码

  @Test
    public void test02() throws Exception {
       InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
        IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
        List<TwoUser> all = mapper.findAll();
        for (TwoUser twoUser : all) {
            System.out.println(twoUser);
        }
    }

3、多对多查询

多对多表示一个用户可以有多个角色,一个角色可以被多个用户使用
所以用户和角色是多对多的关系(多对多其实就是两个一对多

a、多对多的查询模型

Mybatis持久层框架使用 - 图66image.gif

b、创建实体类

一个用户可以有多个角色,所以角色就用roleList来装
Mybatis持久层框架使用 - 图68image.gif

c、创建接口

Mybatis持久层框架使用 - 图70image.gif

d、多对多的配置文件

<mapper namespace="com.shanglin.mapper.IUserMapper">
    <cache></cache>
    <!--这里面是多对多的-->
    <resultMap id="userRoleMap" type="com.shanglin.pojo.ThreeUser">
        <result property="id" column="userid"></result>
        <result property="username" column="username"></result>
        <!--这里对应的是ThreeUser下面的Role实体对应的集合。roleList就是集合的名字-->
        <collection property="roleList" ofType="com.shanglin.pojo.Role">
            <result property="id" column="roleid"></result>
            <result property="roleName" column="roleName"></result>
            <result property="roleDesc" column="roleDesc"></result>
        </collection>
    </resultMap>
      <!-- 查询所有用户的同时查询出每一个用户所属角色-->
    <select id="findAllUserAndRole" resultMap="userRoleMap">
        select * from user u left join sys_user_role ur on u.id = ur.userid
        left join sys_role r on r.id = ur.roleid
    </select>
</mapper>

image.gif

Mybatis的注解开发

1、常用的注解

@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result一起使用, 封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装

2、用注解进行CRUD操作

a、在接口中添加注解

    //添加用户 #{}里面的值要和实体中的属性一致,才可以通过get拿到值
    @Insert("insert into user values(#{id},#{username})")
    public void addUser(User user);

    //更新用户
    @Update("update user set username = #{username} where id = #{id}")
    public void updateUser(User user);

    //查询用户
    @Select("select * from user")
    public List<User> selectUser();

    //删除用户
    @Delete("delete from user where id = #{id}")
    public void  deleteUser(Integer id);

b、动态代理来调用方法进行测试

其他的测试代码以此类推

 @Test
    public void test04(){
        IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
        User user = new User();
        user.setId(4);
        user.setUsername("测试数据");
        mapper.addUser(user);
        sqlSession.commit();
    }

c、注解的加载与配置文件加载的异同

sql的核心配置文件中,如果是配置文件,用到resource,或者加载执行方法所在包的全路径
如果是注解,用到class,或者加载执行方法所在包的全路径。如下所示:

 <!--直接引入某个包下所有的接口对应的映射配置文件
  注意前提是:配置文件和接口要同包同名,所以要创建同名包-->
<package name="com.shanglin.mapper"/>

 <!--如果是用注解的话,也是需要添加映射的。就是接口的全路径名。
            但是如果是多个的时候,批量的,那还是要上面的导包形式-->
 <!--<mapper class="com.shanglin.mapper.IUserMapper"/>-->

3、用注解进行复杂映射开发

注解开发也是需要添加映射的,值不过直接添加其注解接口下的包名即可

先看一下配置文件,如同在配置文件的基础上进行转换为注解
image.pngimage.gif

resultMap可用@Results替换
result可用@Result替换
association可用@one替换。
collection可用@Many替换

image.png

4、一对一的注解

image.png

如何用@one进行封装单个对象

public interface IOrderMapper {

    // 查询订单的同时,还需查询订单所属的用户
    // 先查询出订单的uid,然后再发起查询,查询订单关联的用户
    //javaType表示Order实体中 user这个类型是什么
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "orderTime",column = "orderTime"),
            @Result(property = "total",column = "total"),
            @Result(property = "user",column = "uid",javaType = User.class,
                    // 这个sql是根据当前查询结果uid的值,再次查询下面那个订单关联的用户
                    // one这个是对一方的查询。select里面就是一个namespace.id=>接口全路径.方法名
                    one=@One(select = "com.shanglin.mapper.IUserMapper.findUserById"))
    })
    @Select("select * from orders")  // 先执行这条查询
    public List<Order> findOrderAndUser();
}

image.gif

public interface IUserMapper {     
    //根据id查询用户
    @Options(useCache = true)
    @Select({"select * from user where id = #{id}"})
    public User findUserById(Integer id);
}

调用方法测试

    @Test
    public void ontToOne(){
        IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class);
        List<Order> orderAndUser = mapper.findOrderAndUser();
        for (Order order : orderAndUser) {
            System.out.println(order);
        }
    }

image.gif

5、一对多的注解

查询所有用户,及查询出用户所关联的所有订单。

a、定义好实体类

一个用户多个订单,那么就需要在user实体类中,添加List来 表示多个订单
image.png
image.gif

b、如何使用many进行封装集合

public interface IUserMapper
// 查询所有用户,同时查询每个用户关联的订单信息(一对多)
    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "username",column = "username"),
            // 第三个property是从user实体中,找到Order集合的属性名orderList
            // javaType指明当前property属性的类型,当前是list集合类型的
            @Result(property = "orderList",column = "id",javaType = List.class,
                    // 这个list是一对多的,是根据用户的id进行查询
                    many=@Many(select = "com.shanglin.mapper.IOrderMapper.findOrderByUid"))
    })
    public List<TwoUser> findAll();
}

image.gif

public interface IOrderMapper {
    @Select("select * from orders where uid = #{uid}")
    public List<Order> findOrderByUid(Integer uid);
}

c、测试

@Test
    public void ontToMany(){
        IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
        List<TwoUser> all = mapper.findAll();
        for (TwoUser twoUser : all) {
            System.out.println(twoUser);
        }
    }

image.gif

6、多对多的注解开发

查询出所有用户,及查询出所有用户所关联的角色。

a、创建实体类

image.png

b、 @Many如何实现多对多

//查询所用用户,同时查询每个用户关联的角色信息(多对多)
    // 先查询出这个sql,因为用户表和角色表有关联关系,这个关系由中间表来维护
    // 第一个sql执行后,有了角色id,然后根据角色id值进行查询,查出该用户的角色信息
    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "username",column = "username"),
            @Result(property = "roleList",column = "id",javaType = List.class,
                    many = @Many(select = "com.shanglin.mapper.IRoleMapper.findRoleByUid"))
    })
    public List<ThreeUser> findAllUserAndRole();

image.gif

public interface IRoleMapper {
    // 根据用户的id进行关联查询,把用户的角色信息查出也是第二次查询
    @Select("select * from sys_role r,sys_user_role ur where r.id = ur.roleid and ur.userid = #{uid}")
    public List<Role> findRoleByUid(Integer uid);
}

image.gif

c、测试

@Test
    public void ManyToMany(){
        IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
        List<ThreeUser> allUserAndRole = mapper.findAllUserAndRole();
        for (ThreeUser tUser : allUserAndRole) {
            System.out.println(tUser);
        }
    }

image.gif

Mybatis的缓存

缓存就是内存中的数据,常常来自对数据库查询结果的保存,使用缓存,我们可以避免频繁的与数据库进行交互,进而提高响应速度
mybatis也提供了对缓存的支持, 分为一级缓存和二级缓存, 可以通过下图来理解:

image.pngimage.gif

①、一级缓存是Sql Session级别的缓存。在操作数据库时需要构造sql Session对象, 在对象中有一个数据结构(HashMap) 用于存储缓存数据。不同的sql Session之间的缓存数据区域(HashMap) 是互相不影响的。

②、二级缓存是mapper级别的缓存, 多个SqlSession去操作同一个Mapper的sql语句, 多个SqlSession可以共用二级缓存, 二级缓存是跨Sql Session的

image.gif

一级缓存

通过会话对象SQLSession去查询验证缓存
Mybatis持久层框架使用 - 图90image.gif

③、总结
1、第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
2、如果中间sq Session去执行commit操作(执行插入、更新、删除) , 则会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
3、第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息

image.png


一级缓存原理探究与源码分析

一级缓存到底是什么?一级缓存什么时候被创建?一级缓存的工作流程是怎样的?
上面的一级缓存,都绕不开SqlSession。所以从SqlSession开始找
多看一下代码,点进去。
1、一级缓存就是一个hashMap
2、

二级缓存

二级缓存缓存的是数据,不是对象,因为缓存是数据,当第二次查询时候,如果是相同的,则重新创建一个对象,将数据赋值给它,然后返回。

二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于sqlSession的, 而二级缓存是基于mapper文件的namespace的, 也就是说多个sqlSession可以共享一个mapper中的二级缓存区域, 并且如果两个mapper的namespace相同, 即使是两个mapper, 那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中

image.png

二级缓存的使用

1、添加二级缓存标签

image.pngimage.gif

2、对实体类进行序列化

开启了二级缓存后, 还需要将要缓存的pojo实现Serializable接口, 为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取这个缓存的话, 就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口image.gif
image.png

3、测试

 @Test
    public  void secondCatch()  {
        //根据sqlSessionFactory 生产session 会话对象
        SqlSession sqlSession1 = sessionFactory.openSession();
        SqlSession sqlSession2 = sessionFactory.openSession();
        SqlSession sqlSession3 = sessionFactory.openSession();

       // String statement = "com.lagou.pojo.UserMapper.selectUserByUserId" ;
        IUserMapper userMapper1 = sqlSession1.getMapper(IUserMapper.class );
        IUserMapper userMapper2 = sqlSession2.getMapper(IUserMapper.class );
        IUserMapper userMapper3 = sqlSession2.getMapper(IUserMapper.class );
        //第一次查询,命中率为0,会发出sql,将查询结果放到缓冲中
        User u1 = userMapper1.findUserById(1);
        System.out.println(u1);
        sqlSession1.close(); //清空一级缓存

        User user = new User();
        user.setId(1);
        user.setUsername( "aaa" );
        userMapper3.updateUser(user);
        sqlSession3.commit(); // 这步提交后,缓存会被清空

        User u2 = userMapper2.findUserById(1); // 第二次也需要发送sql

    }

4、分析命中率

第一次命中率是0,就是缓存中没有<br />    第二次命中率是0.5,也就是缓存中有了,两次,命中一次,概率就是50%<br />    如果期间进行了更改操作,会清空二级缓存,再次查询还需要从数据库查询。

5、useCache的使用(默认是true)

userCache默认是开启的,默认值是true。
userCache是用来设置是否禁用二级缓存的, 在statement中设置useCache=false可以禁用当前select语句的二级缓存, 即每次查询都会发出sql去查询, 默认情况是true, 即该sql默认去使用二级缓存。
如果将设置成useCache=false, 表示禁用二级缓存, 直接从数据库中获取。
配置文件则如下设置禁用:

<select id="select User By UserId"  useCache="false" 
        resultType="com.shanglin.pojo.User"  parameterType="int">
select * from user where id=#{id}
</select>

注解的按如下设置禁用:
image.png

6、flushCache的使用(默认是true)

在mapper的同一个namespace中, 如果有其它insert、update、delete操作数据后需要刷新缓
存,如果不执行刷新缓存会出现脏读。
也就是增删改后是需要刷新缓存的,而配置中flushCache=”true”属性, 默认情况下为true, 即刷新缓存, 如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。所以呢,这个值一般是不修改的,默认让他刷新缓存

分布式二级缓存整合redis

Mybatis持久层框架使用 - 图99image.gif

Mybatis持久层框架使用 - 图101image.gif

Mybatis持久层框架使用 - 图103image.gif

a、mybatis与redis的整合

刚刚提到过, mybatis提供了一个cache接口, 如果要实现自己的缓存逻辑, 实现cache接口开发即可。
mybatis本身默认实现了一个, 但是这个缓存的实现无法实现分布式缓存, 所以我们要自己来实现。
redis分布式缓存就可以, mybatis提供了一个针对cache接口的redis实现类, 该类存在mybatis-redis包中

b、在pom.xml中添加jar包

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-redis</artifactId>
    <version>1.0.0-beta2</version>
</dependency>

c、基于redis实现二级缓存

1、配置文件的实现

<?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.shanglin.mapper.IUserMapper">
<cache type="org.mybatis.caches.redis.RedisCache" /><!--基于redis实现二级缓存-->
<select id="findAll" resultType="com.shanglin.pojo.User" useCache="true">
 select * from user
</select>

2、注解中的实现
image.png

d、在Resource下面创建redis.properties

redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0

e、代码测试

image.gifimage.gif

@Test
public void SecondLevelCache(){
 SqlSession sqlSession1 = sqlSessionFactory.openSession();
 SqlSession sqlSession2 = sqlSessionFactory.openSession();
 SqlSession sqlSession3 = sqlSessionFactory.openSession();
 IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
 IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class);
 IUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);
 User user1 = mapper1.findUserById(1);
 sqlSession1.close(); //清空一级缓存
 User user = new User();
 user.setId(1);
 user.setUsername("lisi");
 mapper3.updateUser(user);
 sqlSession3.commit();
 User user2 = mapper2.findUserById(1);
 System.out.println(user1==user2);
}

image.gif
执行后,出现一次查询,第二次没有查询,而是命中率为0.5。也就是使用redis作为二级缓存已经OK。

mybatis执行器1

https://www.cnblogs.com/sunanli/p/13550990.html