高级特性,包括关联查询、延迟加载、动态SQL、缓存、逆向工程、PageHelper分页插件
1.关联查询 1.一对一 例子:一个order订单对应一个user
需要创建专门的结果映射类,OrderExt,里边有User属性。
1 2 3 4 public class OrdersExt extends Orders { private User user;// 用户对象 // get/set。。。。 }
resultMap配置普通的OrderExp的属性+User属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <!-- 查询订单关联用户信息使用resultmap --> <resultMap type="OrdersExt" id="ordersAndUserRstMap"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!-- 一对一关联映射 --> <!-- property:Orders对象的user属性 javaType:user属性对应 的类型 --> <association property="user" javaType="com.kkb.mybatis.po.User"> <!-- column:user表的主键对应的列 property:user对象中id属性--> <id column="user_id" property="id"/> <result column="username" property="username"/> <result column="address" property="address"/> </association> </resultMap> <select id="findOrdersAndUserRstMap" resultMap="ordersAndUserRstMap"> SELECT o.id, o.user_id, o.number, o.createtime, o.note, u.username, u.address FROM orders o JOIN `user` u ON u.id = o.user_id </select>
2.一对多 例子:一个用户有多个订单
User类中有List<Orders>
1 2 3 4 5 6 7 8 public class User{ private Integer id; private String username; private String sex; private Date birthday; private String address; private List<Orders> orders; }
resultMap配置User的普通属性+List<Orders>属性
普通属性用result
List<Orders>用collection,collection的返回类型用ofType配置为List内的泛型orders
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <resultMap type="user" id="userAndOrderRstMap"> <!-- 用户信息映射 --> <id property="id" column="id"/> <result property="username" column="username"/> <result property="birthday" column="birthday"/> <result property="sex" column="sex"/> <result property="address" column="address"/> <!-- 一对多关联映射 --> <collection property="orders" ofType="orders"> <id property="id" column="oid"/> <result property="userId" column="id"/> <result property="number" column="number"/> <result property="createtime" column="createtime"/> <result property="note" column="note"/> </collection> </resultMap> <select id="findUserAndOrderRstMap" resultMap="userAndOrderRstMap"> SELECT u.*, o.id oid, o.number, o.createtime, o.note FROM `user` u LEFT JOIN orders o ON u.id = o.user_id </select>
2.延迟加载 延迟加载,也称为懒加载,关联查询时,推迟查询关联对象的查询。
目的:减少数据库的压力。
分类:直接加载、侵入式加载、深度延迟加载
存在的问题:深度加载存在N+1的问题。深度加载的实现是主查询+N次子查询,会发送N次SQL子查询语句。子查询记录比较多时,发送SQL子查询语句很消耗资源。
使用:需要resultMap中的association和collection子标签,主配置文件中settings配置延迟加载lazyLoadingEnabled和aggressiveLazyLoading,resultMap子标签也可开启延迟加载属性fetchType,子标签的fetchType可覆盖全局属性
直接加载:
1 2 3 4 <settings> <!-- 延迟加载总开关 --> <setting name="lazyLoadingEnabled" value="false"/> </settings>
侵入式延迟加载:
1 2 3 4 5 6 <settings> <!-- 延迟加载总开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 侵入式延迟加载开关 --> <setting name="aggressiveLazyLoading" value="true"/> </settings>
深度延迟加载:
1 2 3 4 5 6 <settings> <!-- 延迟加载总开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 侵入式延迟加载开关 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
3.缓存 1.一级缓存 默认开启,作用域:同一个sqlsession中。修改和删除会清除缓存。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void testOneLevelCache() { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库 User user1 = mapper.findUserById(1); System.out.println(user1); // 第二次查询ID为1的用户 User user2 = mapper.findUserById(1); System.out.println(user2); sqlSession.close(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void testOneLevelCache() { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库 User user1 = mapper.findUserById(1); System.out.println(user1); User user = new User(); user.setUsername("隔壁老詹1"); user.setAddress("洛杉矶湖人"); //执行增删改操作,清空缓存 mapper.insertUser(user); // 第二次查询ID为1的用户 User user2 = mapper.findUserById(1); System.out.println(user2); sqlSession.close(); }
2.二级缓存 默认不开启,作用域:同一个Mapper(namespace)中。不同的sqlsseion可查询到同一个Mapper缓存中的数据。
开启方式:全局配置settings+namespace配置。
1 2 3 4 <!-- 开启二级缓存总开关 --> <settings> <setting name="cacheEnabled" value="true"/> </settings>
1 2 <!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache --> <cache></cache>
一般不用,二级缓存控制粒度较粗,同一个namespace中执行任何增删改都会清除缓存。
二级缓存的数据不一定都是存储到内存中 ,比如文件系统 ,二级缓存相关的类和父类需要实现序列化。
一种办法:namespace中只存放查询语句。不清楚其他地方修改了数据库中此namespace中的记录,是否会读到脏数据?
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void testTwoLevelCache() { SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class); // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库 User user1 = mapper1.findUserById(1); System.out.println(user1); // 关闭SqlSession1 sqlSession1.close(); // 第二次查询ID为1的用户 User user2 = mapper2.findUserById(1); System.out.println(user2); // 关闭SqlSession2 sqlSession2.close(); }
4.动态SQL 解决:字符串的拼接处理、循环判断。
1.if标签 queryvo中封装有user对象
1 2 3 4 5 6 7 8 <select id="findUserList" parameterType="queryVo" resultType="user"> SELECT * FROM user where 1=1 <if test="user != null"> <if test="user.username != null and user.username != ''"> AND username like '%${user.username}%' </if> </if> </select>
2.where标签 解决 if标签内容开头规定为AND,where 后需要 1 = 1,解决1=1的性能问题 。1=1时不使用索引
1 2 3 4 5 6 7 8 9 10 11 <select id="findUserList" parameterType="queryVo" resultType="user"> SELECT * FROM user <!-- where标签会处理它后面的第一个and --> <where> <if test="user != null"> <if test="user.username != null and user.username != ''"> AND username like '%${user.username}%' </if> </if> </where> </select>
3.SQL片段复用 include标签,提取可复用代码
1 2 3 4 5 6 7 <sql id="query_user_where"> <if test="user != null"> <if test="user.username != null and user.username != ''"> AND username like '%${user.username}%' </if> </if> </sq
使用include标签
1 2 3 4 5 6 7 8 9 <!-- 使用包装类型查询用户 使用ognl从对象中取属性值,如果是包装对象可以使用.操作符来取内 容部的属性 --> <select id="findUserList" parameterType="queryVo" resultType="user"> SELECT * FROM user <!-- where标签会处理它后面的第一个and --> <where> <include refid="query_user_where"></include> </where> </select>
引用其他Mapper标签
1 <include refid="namespace.sql片段”/>
4.foreach标签 需求:综合查询时,传入多个id查询用户信息
1 SELECT * FROM USER WHERE username LIKE '%老郭%' AND id IN (1,10,16)
pojo中添加List<Interger> ids属性
1 2 3 4 public class QueryVo { private User user; private List<Interger> ids; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <sql id="query_user_where"> <if test="user != null"> <if test="user.username != null and user.username != ''"> AND username like '%${user.username}%' </if> </if> <if test="ids != null and ids.size() > 0"> <!-- collection: 指定输入的集合参数的参数名称 --> <!-- item: 声明集合参数中的元素变量名 --> <!-- open: 集合遍历时,需要拼接到遍历sql语句的前面 --> <!-- close: 集合遍历时,需要拼接到遍历sql语句的后面 --> <!-- separator: 集合遍历时,需要拼接到遍历sql语句之间的分隔符号 --> <foreach collection="ids" item="id" open=" AND id IN ( " close=" ) " separator=","> #{id} </foreach> </if> </sql>
5.逆向工程 功能: 可以依据数据库表生成User实体类、UserExample扩展类、UserMapper接口、UserMapper.xml。UserMapper.xml包括增删改查。不可自动实现部分:一对一、一对多映射。
如何使用: 导入逆向工程项目,配置生成的各个类的路径,执行逆向工程main函数。
配置文件:
修改要生成的数据库表
pojo文件所在包路径
Mapper所在的包路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <?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> <context id="testTables" targetRuntime="MyBatis3"> <commentGenerator><!-- 是否去除自动生成的注释 true: 是 : false:否 --> <property name="suppressAllComments" value="true" /> </commentGenerator> <!--数据库连接的信息:驱动类、连接地址、用户名、密码 --> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/ssm" userId="root" password="root"> </jdbcConnection> <!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver" connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg" userId="yycg" password="yycg"> </jdbcConnection> --> <!-- 默认false, 把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer, 为 true时 把JDBC DECIMAL和 NUMERIC 类型解析为java.math.BigDecimal --> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!-- targetProject:生成PO类的位置 --> <javaModelGenerator targetPackage="com.kkb.ms.po" targetProject=".\src"> <!-- enableSubPackages:是否让schema作为包的后缀 --> <property name="enableSubPackages" value="false" /> <!-- 从数据库返回的值被清理前后的空格 --> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- targetProject:mapper映射文件生成的位置 --> <sqlMapGenerator targetPackage="com.kkb.ms.mapper" targetProject=".\src"> <!-- enableSubPackages:是否让schema作为包的后缀 --> <property name="enableSubPackages" value="false" /> </sqlMapGenerator> <!-- targetPackage: mapper接口生成的位置 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.kkb.ms.mapper" targetProject=".\src"> <!-- enableSubPackages:是否让schema作为包的后缀 --> <property name="enableSubPackages" value="false" /> </javaClientGenerator> <!-- 指定数据库表 --> <table schema="" tableName="user"></table> <table schema="" tableName="order"></table> </context> </generatorConfiguration>
6.分页插件PageHelper https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/en/HowToUse.md
功能:分页
原理:实现Mybatis预留的分页接口。
使用:
导入jar包
1 2 3 4 5 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.6</version> </dependency>
配置全局配置文件plugins
1 2 3 4 5 6 <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- config params as the following --> <property name="helperDialect" value="mysql"/> </plugin> </plugins>
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //获取第1页, 每页10条内容,默认查询总数count PageHelper.startPage(1, 10); //增加上边一行后,此处的返回的list变为PageHelper提供的Page对象。 List<Country> list = countryMapper.selectAll(); //用PageInfo对结果进行包装 PageInfo page = new PageInfo(list); //测试PageInfo全部属性 //PageInfo包含了非常全面的分页属性 assertEquals(1, page.getPageNum()); assertEquals(10, page.getPageSize()); assertEquals(1, page.getStartRow()); assertEquals(10, page.getEndRow()); assertEquals(183, page.getTotal()); assertEquals(19, page.getPages()); assertEquals(1, page.getFirstPage()); assertEquals(8, page.getLastPage()); assertEquals(true, page.isFirstPage()); assertEquals(false, page.isLastPage()); assertEquals(false, page.isHasPreviousPage()); assertEquals(true, page.isHasNextPage());
注意:
查询语句是使用resultMap进行的嵌套结果映射,则无法使用PageHelper进行分页
PageHelper 安全调用
不安全:
1 2 3 4 5 6 7 PageHelper.startPage(1, 10); List<User> list; if(param1 != null){ list = userMapper.selectIf(param1); } else { list = new ArrayList<User>(); }
安全:
1 2 3 4 5 6 7 List<User> list; if(param1 != null){ PageHelper.startPage(1, 10); list = userMapper.selectIf(param1); } else { list = new ArrayList<User>(); }