[Java]Mybatis学习笔记(动力节点老杜)(八)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: [Java]Mybatis学习笔记(动力节点老杜)(八)

多对一映射的实现方式

  • 多对一映射的多种方式,常见的包括三种:
  • 第一种方式:一条SQL语句,级联属性映射。
  • 第二种方式:一条SQL语句,association。
  • 第三种方式:两条SQL语句,分步查询。(这种方式常用:优点一是可复用。优点二是支持懒加载。)

第一种方式:级联属性映射

/**
 * 根据id获取学生信息。同时获取学生关联的班级信息。
 * @param id 学生的id
 * @return 学生对象,但是学生对象当中含有班级对象。
 */
Student selectById(Integer id);
<!--多对一映射的第一种方式:一条SQL语句,级联属性映射。-->
<resultMap id="studentResultMap" type="student">
  <!-- column 查询结果的列名,赋值到 property 对象的属性上 -->
  <id property="sid" column="sid"/>
  <result property="sname" column="sname"/>
  <!-- 为clazz(班级对象)的属性赋值 -->
  <result property="clazz.cid" column="cid"/>
  <result property="clazz.cname" column="cname"/>
</resultMap>
<!-- 查询结果会按配置的结果集进行赋值 -->
<!-- 哪个表在前哪个表为主表,多对一,多为主表 -->
<select id="selectById" resultMap="studentResultMap">
  select s.sid, s.sname, c.cid, c.cname
  from t_student s left join t_clazz c
  on s.cid = c.cid
  where s.sid = #{id};
</select>
@org.junit.Test
public void test01() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    Student student = studentMapper.selectById(1);
    System.out.println(student);
}

第二种方式:association

/**
 * 一条SQL语句,association
 * @param id
 * @return
 */
Student selectByIdAssociation(Integer id);
<!-- type 查询结果映射到类型 -->
<resultMap id="studentResultMapAssociation" type="Student">
  <!-- column 查询结果的列名,赋值到 property 对象的属性上 -->
  <id property="sid" column="sid"/>
  <result property="sname" column="sname"/>
  <!--
          association:翻译为关联。一个Student对象关联一个Clazz对象
              property:提供要映射的POJO类的属性名。关联对象引用的属性名
              javaType:用来指定要映射的java类型。
      -->
  <association property="clazz" javaType="Clazz">
    <!-- column 查询结果的列名,赋值到 property 对象的属性上 -->
    <id property="cid" column="cid"/>
    <result property="cname" column="cname"/>
  </association>
</resultMap>
<select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
  select s.sid, s.sname, c.cid, c.cname
  from t_student s left join t_clazz c
                             on s.cid = c.cid
  where s.sid = #{id};
</select>
@org.junit.Test
public void test02() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    Student student = studentMapper.selectByIdAssociation(4);
    System.out.println(student);
}

第三种方式:分步查询 & 多对一延迟加载

  • 先使用一条SQL查询出学生的信息,再使用一条SQL查询出学生信息对应的额班级信息
  • 分步优点:
  • 第一个优点:代码复用性增强。
  • 第二个优点:支持延迟加载。暂时访问不到的数据可以先不查询。提高程序的执行效率。
  • StudentMapper接口中的方法
/**
 * 分部查询第一步:先根据学生的sid查询学生的信息。
 * @param sid
 * @return
 */
Student selectByIdStep1(Integer sid);
  • ClazzMapper接口中的方法
/**
 * 分步查询第二步:根据cid获取班级信息。
 * @param cid
 * @return
 */
Clazz selectByIdStep2(Integer cid);
<!--分步查询第二步:根据cid获取班级信息。-->
<select id="selectByIdStep2" resultType="Clazz">
  select cid, cname
  from t_clazz
  where cid = #{cid};
</select>
<!-- 两条SQL语句,完成多对一的分步查询 -->
<!-- 第一步:根据学生的id查询学生的所有信息。这些信息当中含有班级id(cid) -->
<select id="selectByIdStep1" resultMap="studentResultMapByStep">
  select sid, sname, cid as clazzId
  from t_student
  where sid = #{sid};
</select>
<resultMap id="studentResultMapByStep" type="Student">
  <id property="sid" column="sid"/>
  <result property="sname" column="sname"/>
  <!--
    第二步:根据班级的id查询班级信息
    第一步中查询出来的cid(有起别名使用别名)通过column标签属性
    传给cw.study.mybatis.mapper.ClazzMapper.selectByIdStep2对应的SQL语句
    进行第二步查询,根据班级的id查询班级信息
  -->
  <association
      property="clazz"
      select="cw.study.mybatis.mapper.ClazzMapper.selectByIdStep2"
      column="clazzId"
  ></association>
</resultMap>
@org.junit.Test
public void test03() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    Student student = studentMapper.selectByIdStep1(3);
    System.out.println(student);
}

分步查询分析
  • 分步查询的优点:
  • 第一:复用性增强。可以重复利用。(大步拆成N多个小碎步。每一个小碎步更加可以重复利用。)
  • 第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制。
  • 延迟加载(懒加载)的核心原理是:用的时候再执行查询语句。不用的时候不查询。
  • 作用:提高性能。尽可能的不查,或者说尽可能的少查。来提高效率。
局部懒加载
  • 在mybatis当中怎么开启延迟加载呢?
  • association标签中添加 fetchType=“lazy”
  • 注意:默认情况下是没有开启延迟加载的。需要设置:fetchType=“lazy”
  • 这种在association标签中配置fetchType=“lazy”,是局部的设置,只对当前的association关联的sql语句起作用。
<resultMap id="studentResultMapByStep" type="Student">
  <id property="sid" column="sid"/>
  <result property="sname" column="sname"/>
  <!-- 使用fetchType="lazy"开启局部懒加载 -->
  <!-- fetchType="eager"不开启懒加载 -->
  <association
      property="clazz"
      select="cw.study.mybatis.mapper.ClazzMapper.selectByIdStep2"
      column="clazzId"
      fetchType="lazy"
  ></association>
</resultMap>
@org.junit.Test
public void test03() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    Student student = studentMapper.selectByIdStep1(3);
    // 没有使用到第二条SQL语句查询的信息,不会执行第二条SQL
    System.out.println(student.getSid());
    System.out.println(student.getSname());
    // 需要使用第二条SQL查询的信息时,才会执行
    System.out.println(student.getClazz());
}

全局开启延迟加载
  • 在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全部的延迟加载机制:
  • 在mybatis核心配置文件中添加全局配置:lazyLoadingEnabled=true
  • 实际开发中的模式:把全局的延迟加载打开。如果某一步不需要使用延迟加载,请设置:fetchType=“eager”
<settings>
    <!--延迟加载的全局开关。默认值false不开启。-->
    <!--什么意思:所有只要但凡带有分步的,都采用延迟加载。-->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>
<resultMap id="studentResultMapByStep" type="Student">
  <id property="sid" column="sid"/>
  <result property="sname" column="sname"/>
  <association
      property="clazz"
      select="cw.study.mybatis.mapper.ClazzMapper.selectByIdStep2"
      column="clazzId"
  ></association>
</resultMap>

一对多

  • 多对一的映射实体类设计:
  • 一对多的实现,通常是在一的一方中有List集合属性。
  • 一个班级对应多个学生
  • 在Clazz类中添加List<Student> stus;属性。
/**
 * 班级信息
 */
public class Clazz {
    private Integer cid;
    private String cname;
    private List<Student> stus;
  ......
}
/**
 * 学生信息
 */
public class Student { // Student是多的一方
    private Integer sid;
    private String sname;
    private Clazz clazz; // Clazz是一的一方。
    ......
}

一对多映射的实现方式

  • 一对多的实现通常包括两种实现方式:
  • 第一种方式:collection 标签
  • 第二种方式:分步查询

第一种方式:collection 标签

/**
 * 根据班级编号查询班级信息。
 * @param cid
 * @return
 */
Clazz selectByCollection(Integer cid);
<select id="selectByCollection" resultMap="clazzResultMap">
  select c.cid, c.cname, s.sid, s.sname
  from t_clazz c left join t_student s
  on c.cid = s.cid
  where c.cid = #{cid};
</select>
<resultMap id="clazzResultMap" type="Clazz">
  <id property="cid" column="cid"/>
  <result property="cname" column="cname"/>
  <!-- 
    一对多,使用collection 
    ofType:指定集合中的元素的类型
  -->
  <!--
    不能为Student对象的clazz属性赋值,
    否则会死循环Student中有clazz,clazz中有Student
  -->
  <collection property="stus" ofType="Student">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
  </collection>
</resultMap>
@org.junit.Test
public void test04() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    ClazzMapper clazzMapper = sqlSession.getMapper(ClazzMapper.class);
    Clazz clazz = clazzMapper.selectByCollection(1000);
    System.out.println(clazz.getCid() + " " + clazz.getCname());
    clazz.getStus().forEach(System.out::println);
}

第二种方式:分步查询 & 一对多延迟加载

  • ClazzMapper接口中的方法
/**
 * 分步查询。第一步:根据班级编号获取班级信息。
 * @param cid 班级编号
 * @return
 */
Clazz selectByStep1(Integer cid);
  • StudentMapper接口中的方法
/**
 * 根据班级编号查询学生信息。
 * @param cid
 * @return
 */
List<Student> selectByCidStep2(Integer cid);
<!-- 根据班级编号查询学生信息。 -->
<select id="selectByCidStep2" resultType="Student">
  select sid, sname from t_student where cid = #{cid}
</select>
<!--分步查询第一步:根据班级的cid获取班级信息。-->
<select id="selectByStep1" resultMap="clazzResultMapStep">
  select cid, cname from t_clazz where cid = #{cid};
</select>
<resultMap id="clazzResultMapStep" type="Clazz">
  <id property="cid" column="cid"/>
  <result property="cname" column="cname"/>
  <!--
      第二步:根据班级的id查询学生信息
      第一步中查询出来的cid(有起别名使用别名)通过column标签属性
      传给cw.study.mybatis.mapper.StudentMapper.selectByCidStep2对应的SQL语句
      进行第二步查询,根据班级的id查询学生信息
    -->
  <association
      property="stus"
      select="cw.study.mybatis.mapper.StudentMapper.selectByCidStep2"
      column="cid"
  />
</resultMap>
@org.junit.Test
public void test05() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    ClazzMapper clazzMapper = sqlSession.getMapper(ClazzMapper.class);
    Clazz clazz = clazzMapper.selectByStep1(1001);
    System.out.println(clazz.getCid() + " " + clazz.getCname());
    clazz.getStus().forEach(System.out::println);
}

开启延迟加载
  • 一对多延迟加载机制和多对一是一样的。同样是通过两种方式:
  • 第一种:fetchType=“lazy”
  • 第二种:修改全局的配置setting,lazyLoadingEnabled=true,如果开启全局延迟加载,想让某个sql不使用延迟加载:fetchType=“eager”
<resultMap id="clazzResultMapStep" type="Clazz">
  <id property="cid" column="cid"/>
  <result property="cname" column="cname"/>
  <association
      property="stus"
      select="cw.study.mybatis.mapper.StudentMapper.selectByCidStep2"
      column="cid"
      fetchType="lazy"
  />
</resultMap>
<settings>
  <setting name="lazyLoadingEnabled" value="true"/>
</settings>

多对多

  • 多对多,可以分解为两个一对多进行处理
  • 多对多,可以多一个存储两个表之间数据关系的表,将多对多转化为两个一对多或者两个多对一

MyBatis的缓存

  • 缓存:cache
  • 缓存的作用:把数据保存到内存(缓存)中,下一次要使用的时候,直接从缓存中获取,通过减少IO的方式,来提高程序的执行效率。
  • mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直接从缓存中取,不再查数据库,不再去硬盘上找数据
  • 一方面是减少了IO。另一方面不再执行繁琐的查找算法。效率大大提升。
  • 如果执行完查询的SQL后再执行增删改的SQL,mybatis会将缓存进行清空,再执行相同的查询SQL时,不会再去查找缓存,会从硬盘文件中重新读取,保证使用的数据与数据库一致
  • mybatis缓存包括:
  • 一级缓存:将查询到的数据存储到SqlSession中。
  • 二级缓存:将查询到的数据存储到SqlSessionFactory中。
  • 或者集成其它第三方的缓存:比如EhCache【Java语言开发的】、Memcache【C语言开发的】等。
  • 缓存只针对于DQL语句,也就是说缓存机制只对应select语句

一级缓存

  • 一级缓存默认是开启的。不需要做任何配置。
  • 原理:只要使用同一个SqlSession对象执行同一条SQL语句,就会走缓存。
/**
 * 根据汽车id查询汽车信息
 * @param id
 * @return
 */
Car selectById(Long id);
<select id="selectById" resultType="Car">
  select * from t_car where id = #{id};
</select>
@org.junit.Test
public void test01() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper carMapper = sqlSession.getMapper(CarMapper.class);
    Car car1 = carMapper.selectById(5L);
    System.out.println(car1);
    Car car2 = carMapper.selectById(5L);
    System.out.println(car2);
    SqlSessionUtil.close();
}

不走一级缓存的情况

  • 什么情况下不走缓存?
  • 第一种:不同的SqlSession对象。
  • 第二种:查询条件变化了。
@org.junit.Test
public void test02() throws IOException {
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    CarMapper carMapper1 = sqlSession1.getMapper(CarMapper.class);
    System.out.println(carMapper1.selectById(5L));
  sqlSession1.close();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    CarMapper carMapper2 = sqlSession2.getMapper(CarMapper.class);
    System.out.println(carMapper2.selectById(5L));
  sqlSession2.close();
}

一级缓存失效的情况

  • 一级缓存失效情况包括两种:
  • 第一种:第一次查询和第二次查询之间,手动清空了一级缓存。sqlSession.clearCache();
  • 第二种:第一次查询和第二次查询之间,执行了增删改操作。这个增删改和哪张表没有关系,只要有insert delete update操作,一级缓存就失效,清空一级缓存,保证查出来的数据是对的

二级缓存

  • 二级缓存的范围是SqlSessionFactory。

二级缓存的使用

  • 使用二级缓存需要具备以下几个条件:
  1. <setting name="cacheEnabled" value="true">全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认就是true,无需设置。
  1. 二级缓存默认是开启的
  1. 在需要使用二级缓存的SqlMapper.xml文件中添加配置:<cache />
<!--
    默认情况下,二级缓存机制是开启的。
    只需要在对应的SqlMapper.xml文件中添加以下标签。
    表示要在该SqlMapper.xml文件中使用该二级缓存。
-->
<cache/>
  1. 使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
  2. SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中。此时二级缓存才可用。
  1. SqlSession对象不关闭或不提交,那么一级缓存中的数据不会被写到二级缓存中
public class Car implements Serializable {}
@org.junit.Test
public void test02() throws IOException {
    // 这里只有一个SqlSessionFactory对象,二级缓存对应的就是SqlSessionFactory。
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
    CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
    // 这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。)
    Car car1 = mapper1.selectById(5L);
    System.out.println(car1);
    // 如果执行了这行代码,sqlSession1的一级缓存中的数据会放到二级缓存当中。
    sqlSession1.close();
    // 由于当前执行的SQL和查询条件与二级缓存中的已有的缓存数据一样
    // 直接从二级缓存中获取,不再执行SQL语句进行查询
    Car car2 = mapper2.selectById(5L);
    System.out.println(car2);
    // 程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。
    sqlSession2.close();
}

二级缓存失效的情况

  • 只要两次查询之间出现了增删改操作,二级缓存就会失效,一级缓存也会失效

二级缓存的相关配置

  • eviction(剔除):指定从缓存中移除某个对象的淘汰算法。默认采用LRU策略。
  • LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象。(其实还有一种淘汰算法LFU,最不常用。)
  • 最近最少使用,不一定最少使用,可能只是最近使用少,但是常用
  • LFU,从对象存在开始,最不常用
  • FIFO:First In First Out。一种先进先出的数据缓存器。先进入二级缓存的对象最先被淘汰。先进先出
  • SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
  • WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。
  • flushInterval:
  • 二级缓存的刷新时间间隔。单位毫秒。
  • 如果没有设置。就代表不刷新缓存,只要内存足够大,一直会向二级缓存中缓存数据。除非执行了增删改。
  • 缓存一刷新,缓存也是失效的
  • readOnly:
  • true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好。但是多线程并发可能会存在安全问题。多个线程操作同一个对象
  • false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安全。
  • size:
  • 设置二级缓存中最多可存储的java对象数量。默认值1024。
  • 缓存中的对象数量超过size,就会对对象开始驱逐(eviction)

MyBatis集成EhCache

  • 老杜原版笔记集成EhCache步骤:【https://www.yuque.com/zuihoudewu/java_note/mt2812#IB2eW】
  • 集成EhCache是为了代替mybatis自带的二级缓存,一级缓存是无法替代的。
  • mybatis对外提供了接口,也可以集成第三方的缓存组件。比如EhCache、Memcache等。都可以。
  • EhCache是Java写的。Memcache是C语言写的。所以mybatis集成EhCache较为常见,按照以下步骤操作,就可以完成集成:
  • 第一步:引入mybatis整合ehcache的依赖。
<!--mybatis集成ehcache的组件-->
<dependency>
  <groupId>org.mybatis.caches</groupId>
  <artifactId>mybatis-ehcache</artifactId>
  <version>1.2.2</version>
</dependency>

  • 第二步:在类的根路径下新建echcache.xml文件,并提供以下配置信息。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存-->
    <diskStore path="e:/ehcache"/>
    <!--defaultCache:默认的管理策略-->
    <!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
    <!--maxElementsInMemory:在内存中缓存的element的最大数目-->
    <!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
    <!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-->
    <!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问-->
    <!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问-->
    <!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
    <!--FIFO:first in first out (先进先出)-->
    <!--LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存-->
    <!--LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存-->
    <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
                  timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
</ehcache>
  • 第三步:修改SqlMapper.xml文件中的标签,添加type属性。
<!-- <cache/> 表示使用mybatis自己的缓存 -->
<!-- 集成使用第三方EhCache缓存 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  • 第四步:编写测试程序使用。
@org.junit.Test
public void test02() throws IOException {
    // 这里只有一个SqlSessionFactory对象,二级缓存对应的就是SqlSessionFactory。
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
    CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
    // 这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。)
    Car car1 = mapper1.selectById(5L);
    System.out.println(car1);
    // 如果执行了这行代码,sqlSession1的一级缓存中的数据会放到二级缓存当中。
    sqlSession1.close();
    // 由于当前执行的SQL和查询条件与二级缓存中的已有的缓存数据一样
    // 直接从二级缓存中获取,不再执行SQL语句进行查询
    Car car2 = mapper2.selectById(5L);
    System.out.println(car2);
    // 程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。
    sqlSession2.close();
}

MyBatis的逆向工程

  • 所谓的逆向工程是:根据数据库表逆向动态生成Java的pojo类、SqlMapper.xml文件以及Mapper接口类等。
  • 要完成这个工作,需要借助别人写好的逆向工程插件。
  • 思考:使用这个插件的话,需要给这个插件配置哪些信息?
  • 1.连接数据库的信息。
  • 2.指定哪些表参与逆向工程。
  • 3.pojo类名、包名以及生成位置。
  • 4.SqlMapper.xml文件名以及生成位置。
  • 5.Mapper接口名以及生成位置。

逆向工程的使用(基础版)

  • 第一步:基础环境准备,新建模块,打包方式
  • 第二步:在pom中添加配置逆向工程的插件(project标签下)
<!--定制构建过程-->
<build>
  <!--可配置多个插件-->
  <plugins>
    <!--其中的一个插件:mybatis逆向工程插件-->
    <plugin>
      <!--插件的GAV坐标-->
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-maven-plugin</artifactId>
      <version>1.4.1</version>
      <!--
        允许覆盖
        会将原先生成的删除,然后覆盖原先的重新生成
      -->
      <configuration>
        <overwrite>true</overwrite>
      </configuration>
      <!--插件的依赖-->
      <dependencies>
        <!--
          mysql驱动依赖
          根据数据库表动态生成,需要连接数据库
        -->
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.30</version>
        </dependency>
      </dependencies>
    </plugin>
  </plugins>
</build>
  • 第三步:配置generatorConfig.xml
  • generatorConfig 生成配置
  • 该文件名必须叫做:generatorConfig.xml
  • 该文件必须放在类的根路径下。
  • 使用这个插件需要给这个插件配置哪些信息?
  • 1.连接数据库的信息。
  • 2.指定哪些表参与逆向工程。
  • 3.pojo类名、包名以及生成位置。
  • 4.SqlMapper.xml文件名以及生成位置。
  • 5.Mapper接口名以及生成位置。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
        targetRuntime有两个值:
            MyBatis3Simple:生成的是基础版,只有基本的增删改查。
            MyBatis3:生成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!--防止生成重复代码-->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
        <!-- 注释信息的生成 -->
        <commentGenerator>
            <!--是否去掉生成日期-->
            <property name="suppressDate" value="true"/>
            <!--是否去除注释-->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--连接数据库信息-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/powernode"
                        userId="root"
                        password="root">
        </jdbcConnection>
        <!-- 生成pojo包名和位置 -->
        <javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
            <!--是否开启子包-->
            <!--如果没有开启子包,com.powernode.mybatis.pojo整个会被作为一个文件夹名-->
            <property name="enableSubPackages" value="true"/>
            <!--是否去除字段名的前后空白-->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- 生成SQL映射文件的包名和位置 -->
        <sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
            <!--是否开启子包-->
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- 生成Mapper接口的包名和位置 -->
        <javaClientGenerator
                type="xmlMapper"
                targetPackage="com.powernode.mybatis.mapper"
                targetProject="src/main/java">
            <!--是否开启子包-->
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- 表名和对应的实体类名-->
        <table tableName="t_car" domainObjectName="Car"/>
    </context>
</generatorConfiguration>
  • 第四步:运行插件

逆向工程的使用(增强版)

  • generatorConfig.xml配置文件中targetRuntime的值选择MyBatis3,生成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
<!--
    targetRuntime有两个值:
        MyBatis3Simple:生成的是基础版,只有基本的增删改查。
        MyBatis3:生成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
  • 生成的XxxxExample类负责封装查询条件,where字句中的查询条件
public class CarMapperTest {
    // CarExample类负责封装查询条件的。
    @Test
    public void testSelect(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 执行查询
        // 1. 查询一个
        Car car = mapper.selectByPrimaryKey(165L);
        System.out.println(car);
        // 2. 查询所有(selectByExample,根据条件查询,如果条件是null表示没有条件。)
        List<Car> cars = mapper.selectByExample(null);
        cars.forEach(car1 -> System.out.println(car1));
        System.out.println("=========================================");
        // 3. 按照条件进行查询
        // QBC 风格:Query By Criteria 一种查询方式,比较面向对象,看不到sql语句。
        // QBC 面向对象查询
        // 封装条件,通过CarExample对象来封装查询条件
        CarExample carExample = new CarExample();
        // 调用carExample.createCriteria()方法来创建查询条件
        // 继续向后调用方法追加查询条件
        carExample.createCriteria()
                   .andBrandLike("帕萨特") // 模糊查询
                   .andGuidePriceGreaterThan(new BigDecimal(20.0)); // 并且价格大于20
        // 添加or
        // 上面添加的为and条件,or()开始添加or条件
        // (... and ... 前面的查询条件) or (...) 生成后的查询条件
        carExample.or().andCarTypeEqualTo("燃油车");
        // 执行查询
        // 更加封装的条件进行查询
        List<Car> cars2 = mapper.selectByExample(carExample);
        cars2.forEach(car2 -> System.out.println(car2));
        sqlSession.close();
    }
}

MyBatis 使用 PageHelper

limit分页

limit

  • 分页查询图示:
  • mysql的limit的语法格式:limit 开始下标, 显示的记录条数
  • mysql的limit后面两个数字:
  • 第一个数字:startIndex,起始下标,下标从0开始,第一条记录的下标是0
  • 第二个数字:pageSize,每页显示的记录条数,从startIndex开始查询出pageSize条记录
  • limit后面只写一个数字,limit 显示的记录条数,相当于limit 0, 显示的记录条数,即从第一条记录开始显示指定数目的记录条数
  • 假设已知页码pageNum,还有每页显示的记录条数pageSize,第一个数字可以动态的获取吗?
  • startIndex = (pageNum - 1) * pageSize
  • 所以,标准通用的mysql分页SQL:
select 
  * 
from 
  tableName ...... 
limit 
  (pageNum - 1) * pageSize, pageSize

mybatis 利用 limit 实现分页查询

  • 使用mybatis应该怎么做?
/**
 * 分页查询
 * @param startIndex 起始下标
 * @param pageSize 每页显示的记录条数
 * @return
 */
List<Car> selectByPage(@Param("startIndex") int startIndex, @Param("pageSize") int pageSize);
<select id="selectByPage" resultType="Car">
    select * from t_car limit #{startIndex}, #{pageSize}
</select>
@Test
public void testSelectByPage(){
    // 获取每页显示的记录条数
    int pageSize = 3;
    // 显示第几页:页码
    int pageNum = 3;
    // 计算开始下标
    int startIndex = (pageNum - 1) * pageSize;
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    List<Car> cars = mapper.selectByPage(startIndex, pageSize);
    cars.forEach(car -> System.out.println(car));
    sqlSession.close();
}

PageHelper 插件

  • PageHelper插件,为mybatis写的分页插件
  • 获取数据不难,难的是获取分页相关的数据比较难(是否还有下一页,是否有下一页等),可以借助mybatis的PageHelper插件。
  • 使用PageHelper插件进行分页,更加的便捷。
  • 第一步:引入依赖
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>5.3.1</version>
</dependency>
  • 第二步:在mybatis-config.xml文件中配置插件
  • typeAliases标签下面进行配置:
<!-- 配置mybatis分页拦截器插件,使用PageHelper插件的分页拦截器插件 -->
<plugins>
  <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
  • 第三步:编写Java代码
/**
 * 查询所有的Car,通过分页查询插件PageHelper完成。
 * @return
 */
List<Car> selectAll();
<!-- 使用了PageHelper插件,分页不用我们进行处理,直接查询数据即可 -->
<select id="selectAll" resultType="Car">
    select * from t_car
</select>
  • 要使用PageHelper插件的分页功能,必须在执行DQL语句之前,开启分页功能
@Test
public void testSelectAll(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    // 一定一定一定要注意:在执行DQL语句之前。开启分页功能。
    int pageNum = 2;
    int pageSize = 3;
    // 开启分页功能,参数一:页码,参数二:页面显示的数据条数
    PageHelper.startPage(pageNum, pageSize);
    List<Car> cars = mapper.selectAll();
    //cars.forEach(car -> System.out.println(car));
    // 查询完之后创建PageInfo对象获取分页的相关信息
    // 封装分页信息对象 new PageInfo()
    // PageInfo对象是PageHelper插件提供的,用来封装分页相关的信息的对象。
    // 第一个参数为查询出来的数据,
    // 第二个参数为导航选项的个数(导航有几页)有几个数字页码可以点击直接跳转
    //    页面跳转导航页码数字的个数
    // navigatePages=3, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]
    PageInfo<Car> carPageInfo = new PageInfo<>(cars, 3);
    System.out.println(carPageInfo);
    sqlSession.close();
    /*
    PageInfo{
      pageNum=2, // 页码,第几页
         pageSize=3,  // 每页几条记录
         size=3, 
         startRow=4, // 整个数据表从第几条数据开始
         endRow=6,  // 整个数据表从第几条数据结束
         total=7,  // 整个数据表数据条数
         pages=3, // 页数的个数,有几页
         list = Page{
            count=true, 
              pageNum=2, 
              pageSize=3, 
              startRow=3, 
              endRow=6, 
              total=7, 
              pages=3, 
              reasonable=false, 
              pageSizeZero=false
          }
          // 本次查询出来的数据,当前页的数据
      [Car{id=168, carNum='1204', brand='奥迪Q7', guidePrice=3.0, produceTime='2009-10-11', carType='燃油车'},
        Car{id=169, carNum='1205', brand='朗逸', guidePrice=4.0, produceTime='2001-10-11', carType='新能源'},
        Car{id=170, carNum='1206', brand='奔驰E300L', guidePrice=50.0, produceTime='2003-02-03', carType='新能源'}],
        prePage=1,  // 上一页的页码
        nextPage=3,  // 下一页的页码
        isFirstPage=false,  // 是否是第一页
        isLastPage=false, // 是否是最后一页
        hasPreviousPage=true,  // 是否有上一页
        hasNextPage=true, // 是否有下一页
        navigatePages=3,  // 导航页码的个数
        navigateFirstPage=1,  // 导航页码开始页码
        navigateLastPage=3, // 导航页码结束页码
        navigatepageNums=[1, 2, 3] // 导航页码数字
    }
    */
}

MyBatis的注解式开发

  • mybatis中也提供了注解式开发方式,采用注解可以减少Sql映射xml文件的配置。
  • 当然,使用注解式开发的话,sql语句是写在java程序中的,这种方式也会给sql语句的维护带来成本。
  • 官方是这么说的:
  • 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句
  • 使用注解编写复杂的SQL是这样的:
  • 原则:简单sql可以注解,复杂sql使用xml

@Insert

@Insert("insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);

@Delete

@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);

@Update

@Update("update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}")
int update(Car car);

@Select

// 有启动字段名和属性名自动映射
@Select("select * from t_car where id = #{id}")
Car selectById(Long id);

@Results

@Select("select * from t_car where id = #{id}")
// 没有启动字段名和属性名自动映射
// 定义属性名与字段名的对应关系
@Results({
        @Result(property = "id", column = "id"),
        @Result(property = "carNum", column = "car_num"),
        @Result(property = "brand", column = "brand"),
        @Result(property = "guidePrice", column = "guide_price"),
        @Result(property = "produceTime", column = "produce_time"),
        @Result(property = "carType", column = "car_type")
})
Car selectById(Long id);


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
Java 数据库连接 Maven
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和MyBatis Generator,使用逆向工程来自动生成Java代码,包括实体类、Mapper文件和Example文件,以提高开发效率。
106 2
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
|
16天前
|
分布式计算 Java MaxCompute
ODPS MR节点跑graph连通分量计算代码报错java heap space如何解决
任务启动命令:jar -resources odps-graph-connect-family-2.0-SNAPSHOT.jar -classpath ./odps-graph-connect-family-2.0-SNAPSHOT.jar ConnectFamily 若是设置参数该如何设置
|
23天前
|
搜索推荐 Java 数据库连接
Java|在 IDEA 里自动生成 MyBatis 模板代码
基于 MyBatis 开发的项目,新增数据库表以后,总是需要编写对应的 Entity、Mapper 和 Service 等等 Class 的代码,这些都是重复的工作,我们可以想一些办法来自动生成这些代码。
30 6
|
1月前
|
分布式计算 资源调度 Hadoop
大数据-01-基础环境搭建 超详细 Hadoop Java 环境变量 3节点云服务器 2C4G XML 集群配置 HDFS Yarn MapRedece
大数据-01-基础环境搭建 超详细 Hadoop Java 环境变量 3节点云服务器 2C4G XML 集群配置 HDFS Yarn MapRedece
76 4
|
1月前
|
分布式计算 Java Hadoop
Hadoop-30 ZooKeeper集群 JavaAPI 客户端 POM Java操作ZK 监听节点 监听数据变化 创建节点 删除节点
Hadoop-30 ZooKeeper集群 JavaAPI 客户端 POM Java操作ZK 监听节点 监听数据变化 创建节点 删除节点
62 1
|
2月前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
2月前
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)
本文是Java基础的进阶篇,对异常、集合、泛型、Java8新特性、I/O流等知识进行深入浅出的介绍,并附有对应的代码示例,重要的地方带有对性能、底层原理、源码的剖析。适合Java初学者。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)
|
1月前
|
Java 数据安全/隐私保护
java学习笔记(基础习题)
java学习笔记(基础习题)
33 0
|
1月前
|
Java 程序员 开发工具
java学习笔记
java学习笔记
35 0
|
2月前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit