多对一映射的实现方式
- 多对一映射的多种方式,常见的包括三种:
- 第一种方式:一条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。
二级缓存的使用
- 使用二级缓存需要具备以下几个条件:
<setting name="cacheEnabled" value="true">
全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认就是true,无需设置。
- 二级缓存默认是开启的
- 在需要使用二级缓存的SqlMapper.xml文件中添加配置:
<cache />
<!-- 默认情况下,二级缓存机制是开启的。 只需要在对应的SqlMapper.xml文件中添加以下标签。 表示要在该SqlMapper.xml文件中使用该二级缓存。 --> <cache/>
- 使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
- SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中。此时二级缓存才可用。
- 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);