1.动态SQL语句:
(1) Xml方式。
(2) Annotation方式。
2. MyBatis的缓存。
3. MyBatis的关联查询-多表查询。
4. MyBatis逆向工程。
1. 动态SQL语句
1.1. 动态SQL是什么
1.2. 动态SQL有什么用
1.根据条件组装不同结构的SQL语句,可以提高SQL代码的重用性。
2.满足某些特定需求,如:条件判断查询。
1.3. 基于XML的实现
1.3.1. 标签包括
<sql>:用于声明SQL语句块,在操作标签中通过<include>标签引入。
<if>:类似java if(){},用于判断。
<foreach>:类似java的foreach循环,一般用于批量处理的SQL语句,如批量更新、插入、删除。
<trim>:切割标签,主要用于切割关键字的头和尾的字符。新版Mybatis的使用几率很少。
<set>:使用set标签就是SQL语言的set关键字,可以在update的时候,设置set关键字后面的更新字段,逗号可以自动忽略。
<where>:使用where标签作为SQL语言的where关键字,如果where后面的条件都不成立,忽略where关键字。
<choose> <when><otherwise> : java的if else if... else。
标签 | 属性 | 说明 |
<sql> | id属性 | id:唯一标识一个sql语句 |
<include> | refid | 引用的sql片段的id属性 |
<if> | test属性 | test:判断条件 |
<where> | 无 | 搭配if标签使用 |
<set> | 无 | 搭配if标签使用 |
<trim> | prefix prefixOverrides suffixOverrides | 前缀名(SET、WHERE) 过滤的前缀(AND、OR) 过滤的后缀(,) |
<foreache> | collecton属性、 item属性、 open属性、 close属性、 separator属性 | collection:遍历的参数(集合)。 item:遍历到的元素。 open:遍历前拼接sql片段。 close:遍历后拼接的sql片段。 separator:每次遍历的分隔符。 |
1.3.2. 接口文件
package org.cjw.mapper;import org.apache.ibatis.annotations.Param;import org.cjw.pojo.User;import java.util.List;public interface UserMapper { /** * 根据条件查询结果 * @param user * @return */ List<User> selectByCondition(User user); /** * 根据条件查询结果总数 * @param user * @return */ Long selectTotalByCondition(User user); /** * 修改用户 * @param user * @return */ int updateUserByNotNull(User user); /** * 批量删除用户 * @param ids * @return */ int deleteByIds(@Param("ids")Integer[] ids); /** * 批量插入 * @param users * @return */ int insertByBatch(@Param("users")List<User> users);}
1.3.3. 映射文件
<?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.cjw.mapper.UserMapper"> <!-- 多行查询 resultType : 只是返回的当行数据封装的对象类型,无论单行还是多行查询都必须返回对应的实体类型User 由于配置文件中已经设置了别名,所以这里使用别名 --> <select id="selectByCondition" parameterType="User" resultType="User"> <!-- select * from tb_user where name like concat('%',#{name},'%') or age = #{age} 上述SQL语句的语义来说,是一个静态SQL语句,一开始已经确定SQL的语义 不管有没有数据,都会对全部数据进行修改,如果某一个数据没有,name会自动设置为null 不符合实际场景 解决方案: 使用MyBatis的动态SQL语句 --> select * from tb_user <!-- <include refid=""></include> 包含引入sql片段 refid :被引入的sql片段的id值 --> <include refid="condition_sql"/> </select> <select id="selectTotalByCondition" parameterType="User" resultType="Long"> select count(1) from tb_user <include refid="condition_sql"/> </select> <!-- <sql id=""></sql> 抽取sql片段 id属性:片段的唯一标识,以供其他地方使用 --> <sql id="condition_sql"> <!-- 动态SQL语句 <where>标签 在where内部编写条件, 1,如果只要满足一个条件<where>标签会自动拼接 WHERE 关键字拼接上对应的条件 2,如果条件前面有 OR|AND 关键字,但是是第一个条件,那么会自动删除出这个关键字,以保证语法正确 3,如果一个条件都没有,那么就相当于查询所有数据 --> <where> <if test="name != '' and name != null"> name like concat('%', #{name}, '%') </if> <if test="age != '' and age != null"> and age = #{age} </if> <if test="email != '' and email != null"> and email = #{email} </if> <if test="password != '' and password != null"> and password = #{password} </if> </where> <!-- 另一种写法是使用trim标签: <trim>标签,开发者可以自定义条件,既可以指定where条件也可以指定set关键字条件 <trim prefix="WHERE" prefixOverrides="AND | OR"> prefix : 前缀, 当前如果是条件就用 WERHE 如果使用修改就用 SET prefixOverrides :在 WHERE 关键字后面的第一个条件,如果是 AND|OR 会自动去掉 --> <!--<trim prefix="WHERE" prefixOverrides="AND|OR"> <if test="name != '' and name != null"> name like concat('%', #{name}, '%') </if> <if test="age != '' and name != null"> and age = #{age} </if> <if test="email != '' and email != null"> and email = #{email} </if> <if test="password != '' and password != null"> and password = #{password} </if> </trim>--> </sql> <update id="updateUserByNotNull" parameterType="User"> <!-- update tb_user set name = #{name}, password= #{password}, age = #{age} where id = #{id} 上述SQL语句的语义来说,是一个静态SQL语句,一开始已经确定SQL的语义 不管有没有数据,都会对全部数据进行修改,如果某一个数据没有,name会自动设置为null 不符合实际场景 解决方案: 使用MyBatis的动态SQL语句,<set>标签、<trim>标签 --> <!-- set标签会自动的过滤掉多余的逗号 --> <!--update tb_user <set> <if test="name != '' and name != null">name = #{name},</if> <if test="age != '' and age != null">age = #{age},</if> <if test="email != '' and email != null">email = #{email},</if> <if test="password != '' and password != null">password = #{password}</if> </set> where id = #{id}--> <!-- prefix : 前缀, 当前如果是 条件就用 WERHE 如果使用修改 就用 SET prefixOverrides :如果在 WHRE 关子健 后面的第一个条件,如果是 AND|OR 会自动去掉 suffixOverrides :如果是最后一个条件, 如果是多余的逗号(,) 会自动去掉 --> update tb_user <trim prefix="SET" suffixOverrides=","> <if test="name != '' and name != null">name = #{name},</if> <if test="age != '' and age != null">age = #{age},</if> <if test="email != '' and email != null">email = #{email},</if> <if test="password != '' and password != null">password = #{password}</if> </trim> where id = #{id} </update> <delete id="deleteByIds" parameterType="List"> <!-- delete from tb_user where id in (1,2,3) --> <!-- <foreach collection="" open="" close="" item="" separator="">标签体内容</foreach> MyBatis的for循环标签 collection:循环集合 open:起始括号( close:结束括号 ) item:集合每次循环的数据对应的变量 separator:分割符号: (1,2,3) 数值与数值之间的分隔符 ,逗号 --> delete from tb_user where id in <foreach collection="ids" item="id" open="(" close=")" separator=","> #{id} </foreach> </delete> <insert id="insertByBatch"> insert into tb_user (name, age, email, password) values <foreach collection="users" item="user" separator=","> (#{user.name}, #{user.age}, #{user.email}, #{user.password}) </foreach> </insert></mapper>
1.3.4. 测试代码
@Testpublic void testSelectByCondition() { SqlSession session = MyBatisUtil.openSesion(); UserMapper userMapper = session.getMapper(UserMapper.class); User user = new User(); user.setName("张"); List<User> users = userMapper.selectByCondition(user); for (User temp : users) { System.out.println(temp.getName()); } session.close();}@Testpublic void testSelectTotalByCondition() { SqlSession session = MyBatisUtil.openSesion(); UserMapper userMapper = session.getMapper(UserMapper.class); User user = new User(); user.setName("张"); Long total = userMapper.selectTotalByCondition(user); System.out.println("total: " + total); session.close();}@Testpublic void testUpdateUserByNotNull() { SqlSession session = MyBatisUtil.openSesion(); try { UserMapper userMapper = session.getMapper(UserMapper.class); User user = new User(); user.setId(1L); user.setName("zhangsan"); int row = userMapper.updateUserByNotNull(user); System.out.println("影响的行数: " + row); session.commit(); } catch (Exception e) { e.printStackTrace(); session.rollback(); } finally { session.close(); }}@Testpublic void testDeleteByIds() { SqlSession session = MyBatisUtil.openSesion(); try { UserMapper userMapper = session.getMapper(UserMapper.class); Integer[] ids = {1, 2, 3, 4}; int row = userMapper.deleteByIds(ids); System.out.println("影响的行数: " + row); session.commit(); } catch (Exception e) { e.printStackTrace(); session.rollback(); } finally { session.close(); }}@Testpublic void testInsertByBatch() { SqlSession session = MyBatisUtil.openSesion(); try { UserMapper userMapper = session.getMapper(UserMapper.class); List<User> users = new ArrayList<>(); users.add(new User(null, "张三", 13, "zhangsan@126.com", "zhangsan123123")); users.add(new User(null, "张四", 14, "zhangsi@163.com", "zhangsi123123")); users.add(new User(null, "张五", 15, "zhangwu@qq.com", "zhangwu123123")); users.add(new User(null, "赵六", 16, "zhangliu@126.com", "zhaoliu123123")); userMapper.insertByBatch(users); session.commit(); } catch (Exception e) { e.printStackTrace(); session.rollback(); } finally { session.close(); }}
1.3.5测试结果
1.4. 基于注解方式实现
1. @SelectProvider 动态查询SQL语句对应注解。
2. @InsertProvider 动态插入SQL语句对应注解。
3. @UpdateProvider 动态修改SQL语句对应注解。
4. @DeleteProvider 动态删除SQL语句对应注解。
5. @Param 动态获取参数
1.4.1. 接口映射文件
package org.cjw.mapper;import org.apache.ibatis.annotations.*;import org.cjw.pojo.User;import java.util.List;public interface UserMapper { /** * 根据条件查询结果 * * @param user * @return */ @SelectProvider(type = UserProvider.class, method = "selectByCondition") List<User> selectByCondition(User user); /** * 根据条件查询结果总数 * * @param user * @return */ @SelectProvider(type = UserProvider.class, method = "selectTotalByCondition") Long selectTotalByCondition(User user); /** * 修改用户,参数不为空的数据才会修改 * * @param user * @return */ @UpdateProvider(type = UserProvider.class, method = "updateUserByNotNull") int updateUserByNotNull(User user); /** * 批量删除用户 * * @param ids * @return */ @DeleteProvider(type = UserProvider.class, method = "deleteByIds") int deleteByIds(@Param("ids") Integer[] ids); /** * 批量插入 * * @param users * @return */ @InsertProvider(type = UserProvider.class, method = "testInsertByBatch") int insertByBatch(@Param("users") List<User> users);}
1.4.2. 动态sql语句文件
package org.cjw.mapper;import org.apache.ibatis.annotations.*;import org.cjw.pojo.User;import java.util.List;public interface UserMapper { /** * 根据条件查询结果 * * @param user * @return */ @SelectProvider(type = UserProvider.class, method = "selectByCondition") List<User> selectByCondition(User user); /** * 根据条件查询结果总数 * * @param user * @return */ @SelectProvider(type = UserProvider.class, method = "selectTotalByCondition") Long selectTotalByCondition(User user); /** * 修改用户,参数不为空的数据才会修改 * * @param user * @return */ @UpdateProvider(type = UserProvider.class, method = "updateUserByNotNull") int updateUserByNotNull(User user); /** * 批量删除用户 * 在程序运行过程中已经不存在ids参数,需要如果想要使用ids这个参数中包含的数据时,需要使用@Param注解来标记参数 * 因为在JDK1.7之前,不能通过反射获取方法参数名 * @param ids * @return */ @DeleteProvider(type = UserProvider.class, method = "deleteByIds") int deleteByIds(@Param("ids") Integer[] ids); /** * 批量插入 * 在程序运行过程中已经不存在users参数,需要如果想要使用users这个参数中包含的数据时,需要使用@Param注解来标记参数 * 因为在JDK1.7之前,不能通过反射获取方法参数名 * @param users * @return */ @InsertProvider(type = UserProvider.class, method = "testInsertByBatch") int insertByBatch(@Param("users") List<User> users);}package org.cjw.mapper;import org.apache.ibatis.annotations.*;import org.cjw.pojo.User;import java.util.List;public interface UserMapper { /** * 根据条件查询结果 * * @param user * @return */ @SelectProvider(type = UserProvider.class, method = "selectByCondition") List<User> selectByCondition(User user); /** * 根据条件查询结果总数 * * @param user * @return */ @SelectProvider(type = UserProvider.class, method = "selectTotalByCondition") Long selectTotalByCondition(User user); /** * 修改用户,参数不为空的数据才会修改 * * @param user * @return */ @UpdateProvider(type = UserProvider.class, method = "updateUserByNotNull") int updateUserByNotNull(User user); /** * 批量删除用户 * 在程序运行过程中已经不存在ids参数,需要如果想要使用ids这个参数中包含的数据时,需要使用@Param注解来标记参数 * 因为在JDK1.7之前,不能通过反射获取方法参数名 * @param ids * @return */ @DeleteProvider(type = UserProvider.class, method = "deleteByIds") int deleteByIds(@Param("ids") Integer[] ids); /** * 批量插入 * 在程序运行过程中已经不存在users参数,需要如果想要使用users这个参数中包含的数据时,需要使用@Param注解来标记参数 * 因为在JDK1.7之前,不能通过反射获取方法参数名 * @param users * @return */ @InsertProvider(type = UserProvider.class, method = "testInsertByBatch") int insertByBatch(@Param("users") List<User> users);}
1.4.3. 测试代码同上
1.4.4. 测试结果
同上
1.4.5. SQL类拼接动态sql语句
SQL类提供的API都是用于拼接SQL语句的,每一个方法对应一个子句,换句话说SQL类拼接SQL语句的单位是子句。
查询涉及的子句:select、from、join、left join、right join、where、group by、having、order by、or、and。
1.4.6. 静态SQL语句和动态SQL语句的区别
<mappers> <!-- xml配置文件 --> <mapper resource="org/cjw/mapper/UserMapper.xml"/> <!-- 配置类 --> <mapper class="org.cjw.mapper.UserProvider"/></mappers>
<mappers> <!-- 配置包扫描,自动找寻xml配置文件或者注解类 --> <package name="org.cjw.mapper" /></mappers>
xml文件通过where、set、if、trim、sql、include标签来拼接动态SQL语句。
注解通过SQL类或原生Java代码来拼接动态SQL语句。
xml文件形式的动态SQL语句的拼接在XML文件中完成。
2. 缓存
缓存作用:提高查询的效率。
2.1. 一级缓存
Mybatis的缓存分为一级缓存、二级缓存。
一级缓存:所谓的一级缓存就是会话(SqlSesion对象)级别的缓存,同一个会话中,如果已经查询过的记录会保存一份在内存中,如果会话没有关闭,再次调用同样的查询方法,不会再查询数据库,而是直接从缓存中取出之前查询的记录(类似get请求的缓存机制)。一级缓存默认是打开的,而且是关闭不了的。
2.1.1. 测试代码
@Testpublic void testSelectByCondition() { SqlSession session = MyBatisUtil.openSesion(); UserMapper userMapper = session.getMapper(UserMapper.class); User user = new User(); user.setName("张"); // 第一次查询数据库 List<User> users = userMapper.selectByCondition(user); for (User temp : users) { System.out.println(temp.getName()); } // 第二次查询数据库 List<User> users2 = userMapper.selectByCondition(user); for (User temp : users2) { System.out.println(temp.getName()); } session.close();}
1.关闭会话close()。
2.进行了DML操作,提交了事务commit()。
3.手动清除缓存clearCache()。
@Testpublic void testSelectByCondition() { SqlSession session = MyBatisUtil.openSesion(); UserMapper userMapper = session.getMapper(UserMapper.class); User user = new User(); user.setName("张"); // 第一次查询数据库 List<User> users = userMapper.selectByCondition(user); for (User temp : users) { System.out.println(temp.getName()); } session.close(); session = MyBatisUtil.openSesion(); userMapper = session.getMapper(UserMapper.class); // 第二次查询数据库 List<User> users2 = userMapper.selectByCondition(user); for (User temp : users2) { System.out.println(temp.getName()); } session.close();}
@Testpublic void testSelectByCondition() { SqlSession session = MyBatisUtil.openSesion(); UserMapper userMapper = session.getMapper(UserMapper.class); User user = new User(); user.setName("张"); // 第一次查询数据库 List<User> users = userMapper.selectByCondition(user); for (User temp : users) { System.out.println(temp.getName()); } Integer[] ids = {4}; int rows = userMapper.deleteByIds(ids); System.out.println("影响的行数: " + rows); // 第二次查询数据库 List<User> users2 = userMapper.selectByCondition(user); for (User temp : users2) { System.out.println(temp.getName()); } session.close();}
@Testpublic void testSelectByCondition() { SqlSession session = MyBatisUtil.openSesion(); UserMapper userMapper = session.getMapper(UserMapper.class); User user = new User(); user.setName("张"); // 第一次查询数据库 List<User> users = userMapper.selectByCondition(user); for (User temp : users) { System.out.println(temp.getName()); }// 清除一级缓存 session.clearCache(); // 第二次查询数据库 List<User> users2 = userMapper.selectByCondition(user); for (User temp : users2) { System.out.println(temp.getName()); } session.close();}
2.2. 二级缓存
MyBatis本身并没有实现二级缓存二级缓存需要第三方缓存提供商的支持:Ehcache -第三方缓存(Hibernate框架默认就是支持)
2.2.1. 下载ehcache
2.2.2. 配置开启二级缓存
MyBatis开启二级缓存,新版本已经默认支持开启二级缓存。可以不改。
<settings> <!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/> <!-- 开启日志 --> <setting name="logImpl" value="STDOUT_LOGGING"/></settings>
2.2.3. 导入Ehcache.jar包
2.2.4. Ehcache依赖 slfj 日志框架,必须要导入slfj的两个jar包
2.2.5. 基于XML配置文件的二级缓存
<ehcache> <!-- 缓存的磁盘位置 --> <diskStore path="D:/mybatis_cache"/> <!-- 默认的缓存策略: 如果开发者在某一个需要缓存的文件配置了自定义缓存,就不使用默认的,如果没有配置,就使用默认缓存策略--> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /></ehcache>
<mapper namespace="cn.zj.mybatis.dao.UserMapper"> <!-- 当前表的映射开启支持二级缓存,并设置相关的缓存提供商,以及缓存的相关配置 --> <cache type="org.mybatis.caches.ehcache.EhcacheCache" > <!--最大的空闲时间 --> <property name="timeToIdleSeconds" value="10000"/> <!-- 最大的在线时间 --> <property name="timeToLiveSeconds" value="20000"/> <!-- 内存的大小 b字节 m1 =1024k 1k=1024b --> <property name="maxEntriesLocalHeap" value="2000000"/> <!-- 文件的大小 b字节--> <property name="maxEntriesLocalDisk" value="20000000"/> <!-- 算法 LRU:最少使用优先, "LFU" or "FIFO:先进先出 --> <property name="memoryStoreEvictionPolicy" value="LRU"/> </cache> <select id="selectAll" resultType="User"> select * from user </select></mapper>
PS:因为二级缓存可以缓存到文件(将对象序列化到本地),涉及到对象序列化,那么对应的javaBean对象就必须实现Serializable接口。
public class User implements Serializable {private static final long serialVersionUID = 1L; // get、set方法}
2.2.6. 缓存的命中率
命中率=从缓存中获取数据的次数/查询的总次数
0.5 = 1/2;
0.666666 = 2/3;
@Testpublic void testSelectByCondition() { for (int i = 0; i < 10; i++) { SqlSession session = MyBatisUtil.openSesion(); UserMapper userMapper = session.getMapper(UserMapper.class); User user = new User(); user.setName("张"); userMapper.selectByCondition(user); session.close(); }}
Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.0Opening JDBC ConnectionCreated connection 1472465.Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1677d1]==> Preparing: select * from tb_user WHERE name like concat('%', ?, '%') ==> Parameters: 张(String)<== Columns: id, name, age, email, password<== Row: 1, 张三, 13, zhangsan@126.com, zhangsan123123<== Row: 2, 张四, 14, zhangsi@163.com, zhangsi123123<== Row: 3, 张五, 15, zhangwu@qq.com, zhangwu123123<== Total: 3DEBUG [main] - put added 0 on heapResetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1677d1]Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1677d1]Returned connection 1472465 to pool.Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.5Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.6666666666666666Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.75Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.8Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.8333333333333334Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.8571428571428571Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.875Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.8888888888888888Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.9
从另外一方面来看,用户的每一个查询数据库的操作,都会首先经过二级缓存,如果二级缓存中没有对应的查询结果,那么就查询数据库,并将查询的记录通过一级缓存存储起来,如果还设置了二级缓存,那么也会往二级缓存中存储一份。
2.2.7. 基于注解的二级缓存
package org.cjw.mapper;import org.apache.ibatis.annotations.*;import org.cjw.pojo.User;import java.util.List;@CacheNamespace(blocking = true)public interface UserMapper { /** * 根据条件查询结果 * * @param user * @return */ @SelectProvider(type = UserProvider.class, method = "selectByCondition") List<User> selectByCondition(User user); /** * 根据条件查询结果总数 * * @param user * @return */ @SelectProvider(type = UserProvider.class, method = "selectTotalByCondition") Long selectTotalByCondition(User user); /** * 修改用户,参数不为空的数据才会修改 * * @param user * @return */ @UpdateProvider(type = UserProvider.class, method = "updateUserByNotNull") int updateUserByNotNull(User user); /** * 批量删除用户 * 在程序运行过程中已经不存在ids参数,需要如果想要使用ids这个参数中包含的数据时,需要使用@Param注解来标记参数 * 因为在JDK1.7之前,不能通过反射获取方法参数名 * @param ids * @return */ @DeleteProvider(type = UserProvider.class, method = "deleteByIds") int deleteByIds(@Param("ids") Integer[] ids); /** * 批量插入 * 在程序运行过程中已经不存在users参数,需要如果想要使用users这个参数中包含的数据时,需要使用@Param注解来标记参数 * 因为在JDK1.7之前,不能通过反射获取方法参数名 * @param users * @return */ @InsertProvider(type = UserProvider.class, method = "testInsertByBatch") int insertByBatch(@Param("users") List<User> users);}
2.2.8. 缓存命中率
Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.0Opening JDBC ConnectionCreated connection 84739718.Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@50d0686]==> Preparing: select * from tb_user where 1 = 1 and name like concat('%', ?,'%') ==> Parameters: 张(String)<== Columns: id, name, age, email, password<== Row: 1, 张三, 13, zhangsan@126.com, zhangsan123123<== Row: 2, 张四, 14, zhangsi@163.com, zhangsi123123<== Row: 3, 张五, 15, zhangwu@qq.com, zhangwu123123<== Total: 3Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@50d0686]Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@50d0686]Returned connection 84739718 to pool.Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.5Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.6666666666666666Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.75Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.8Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.8333333333333334Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.8571428571428571Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.875Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.8888888888888888Cache Hit Ratio [org.cjw.mapper.UserMapper]: 0.9
3. MyBatis的对象关系映射(难点重点)
我们都知道一个表对应一个POJO对象,但是对象与对象之间是没有外键关系的,对象和对象之间只有依赖关系。
MyBatis框架支持多表查询封装对象之间关系,主要使用collection和associatiion标签。
<collection>一对多查询
<association>多对一和一对一查询
3.1. 准备多表,表之间有外键关系(员工表和部门表)
员工表CREATE TABLE `employee` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL, `dept_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;部门表CREATE TABLE `department` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
3.2. 基于XML配置的关系映射
3.2.1. 多对一查询
3.2.1.1. N+1方式
N+1:N 就是当前需要查询结果对应发送的SQL语句的条数
+1 关联查询数据需要额外多发一条SQL语句才能查询出对应的结果
3.2.1.1.1. POJO
package org.cjw.pojo;public class Employee { private Long id; private String name; // 以员工为中心:多个员工对应一个部门,多对一关系,many2noe private Department dept; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Department getDept() { return dept; } public void setDept(Department dept) { this.dept = dept; }}
package org.cjw.pojo;public class Department { private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; }}
3.2.1.1.2. 映射接口
package org.cjw.mapper;import org.cjw.pojo.Department;public interface EmployeeMapper { Department selectEmployeeByPrimaryKey(Long id);}
3.2.1.1.3. 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.cjw.mapper.EmployeeMapper"> <!-- 查询主表一条sql --> <select id="selectEmployeeByPrimaryKey" resultMap="emp_map"> select * from employee where id = #{emp_id} </select> <resultMap id="emp_map" type="Employee"> <id property="id" column="id"/> <result property="name" column="name" /> <!-- association标签的作用:将主表某一列的值作为查询条件查询副表,并将查询结果封装成对象返回 properties属性:最后映射的对象名 column属性:副表查询条件 --> <association property="dept" column="dept_id" select="org.cjw.mapper.EmployeeMapper.selectDeptById" /> </resultMap> <!-- 查询副表N条sql --> <select id="selectDeptById" resultType="Department"> select * from department where id = #{dept_id} </select></mapper>
3.2.1.1.4. 测试代码
@Testpublic void testSelectEmployeeByPrimaryKey() { SqlSession session = MyBatisUtil.openSession(); EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); Employee employee = employeeMapper.selectEmployeeByPrimaryKey(1L); System.out.println("姓名:" + employee.getName() + ", 部门:" + employee.getDept().getName());}
3.2.1.1.5. 测试结果
姓名:张三, 部门:销售部
3.2.1.2. 等值连接查询
3.2.1.2.1. 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.cjw.mapper.EmployeeMapper"> <!-- 查询主表一条sql --> <select id="selectEmployeeByPrimaryKey" resultMap="emp_map"> select e.id e_id, e.name e_name, d.id d_id, d.name d_name from employee e left join department d on e.dept_id = d.id where e.id = #{emp_id} </select> <resultMap id="emp_map" type="Employee"> <id property="id" column="e_id"/> <result property="name" column="e_name" /> <collection property="dept" ofType="Department"> <id property="id" column="d_id" /> <result property="name" column="d_name" /> </collection> </resultMap></mapper>
3.2.1.2.2. 测试结果
姓名:张三, 部门:销售部
3.2.2. 一对多查询
3.2.2.1. N+1方式
3.2.2.1.1. POJO
package org.cjw.pojo;public class Employee { private Long id; private String name; private Long deptId; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Long getDeptId() { return deptId; } public void setDeptId(Long deptId) { this.deptId = deptId; }}
package org.cjw.pojo;import java.util.List;public class Department { private Long id; private String name; // 以部门为中心:一个部门对应多个与员工,一对多关系 private List<Employee> emps; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Employee> getEmps() { return emps; } public void setEmps(List<Employee> emps) { this.emps = emps; }}
3.2.2.1.2. 映射接口
package org.cjw.mapper;import org.cjw.pojo.Department;public interface DepartmentMapper { Department selectDeptById(Long id);}
3.2.2.1.3. 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.cjw.mapper.DepartmentMapper"> <!-- 查询主表一条sql --> <select id="selectDeptById" resultMap="dept_map"> select * from department where id = #{dept_id} </select> <resultMap id="dept_map" type="Department"> <id property="id" column="id"/> <result property="name" column="name" /> <!-- association标签:用于将主表查询结果的某列数据值作为查询副表的查询条件 properties属性:需要映射的对象/集合 column属性:查询条件值 select属性:执行的查询语句 --> <association property="emps" column="id" select="org.cjw.mapper.DepartmentMapper.selectEmpsByDeptId" /> </resultMap> <select id="selectEmpsByDeptId" resultType="Employee"> select * from employee where dept_id = #{id} </select></mapper>
3.2.2.1.4. 测试代码
@Testpublic void testSelectDeptById() { SqlSession session = MyBatisUtil.openSession(); DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class); Department dept = departmentMapper.selectDeptById(1L); for (Employee emp : dept.getEmps()) { System.out.println(emp.getName()); }
}
3.2.2.1.5. 测试结果
张三李四
3.2.2.2. 等值连接查询
3.2.2.2.1. 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.cjw.mapper.DepartmentMapper"> <!-- 查询主表一条sql --> <select id="selectDeptById" resultMap="dept_map"> select d.id d_id, d.name d_name, e.id e_id, e.name e_name from department d left join employee e on d.id = e.dept_id where d.id = #{dept_id} </select> <resultMap id="dept_map" type="Department"> <id property="id" column="d_id"/> <result property="name" column="d_name" /> <collection property="emps" column="id" ofType="Employee"> <id property="id" column="e_id"/> <result property="name" column="e_name" /> <result property="deptId" column="d_id" /> </collection> </resultMap></mapper>
3.2.2.2.2. 测试结果
张三李四
3.3. 基于注解配置的关系映射
3.3.1. 多对一查询
3.3.1.1. N+1方式
3.3.1.1.1. POJO
package org.cjw.pojo;public class Employee { private Long id; private String name; private Department dept; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Department getDept() { return dept; } public void setDept(Department dept) { this.dept = dept; }}
package org.cjw.pojo;public class Department { private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; }}
3.3.1.1.2. 映射接口+注解配置
package org.cjw.mapper;import org.apache.ibatis.annotations.*;import org.cjw.pojo.Department;import org.cjw.pojo.Employee;public interface EmployeeMapper { @Select("select * from employee where id = #{id}") @Results({ @Result(id = true, property = "id", column = "id"), @Result(property = "name", column = "name"), @Result(property = "dept", column = "dept_id", many = @Many(select = "org.cjw.mapper.EmployeeMapper.selectDeptById")) }) Employee selectEmployeeByPrimaryKey(Long id); @Select("select * from department where id = #{dept_id}") Department selectDeptById(Long id);}
3.3.1.1.3. 测试代码
@Testpublic void testSelectDeptById() { SqlSession session = MyBatisUtil.openSession(); EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); Employee employee = employeeMapper.selectEmployeeByPrimaryKey(1L); System.out.println("姓名:" + employee.getName() + ",部门:" + employee.getDept().getName());}
3.3.1.1.4. 测试结果
姓名:张三,部门:销售部
3.3.1.2. 等值连接查询
3.3.2. 一对多查询
3.3.2.0.1. POJO
package org.cjw.pojo;public class Employee { private Long id; private String name; private Long dept_id; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Long getDept_id() { return dept_id; } public void setDept_id(Long dept_id) { this.dept_id = dept_id; }}
package org.cjw.pojo;import java.util.List;public class Department { private Long id; private String name; private List<Employee> emps; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Employee> getEmps() { return emps; } public void setEmps(List<Employee> emps) { this.emps = emps; }}
3.3.2.0.2. 映射接口+注解配置
package org.cjw.mapper;import org.apache.ibatis.annotations.Many;import org.apache.ibatis.annotations.Result;import org.apache.ibatis.annotations.Results;import org.apache.ibatis.annotations.Select;import org.cjw.pojo.Department;import org.cjw.pojo.Employee;import java.util.List;public interface DepartmentMapper { @Select("select * from department where id = #{dept_id}") @Results({ @Result(id = true, property = "id", column = "id"), @Result(property = "name", column = "name"), @Result(property = "emps", column = "id", many = @Many(select = "org.cjw.mapper.DepartmentMapper.selectEmpsByDeptId")) }) Department selectDeptById(Long id); @Select("select * from employee where dept_id = #{dept_id}") List<Employee> selectEmpsByDeptId(Long id);}
3.3.2.0.3. 测试代码
@Testpublic void testSelectDeptById() { SqlSession session = MyBatisUtil.openSession(); DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class); Department dept = departmentMapper.selectDeptById(1L); for (Employee emp : dept.getEmps()) { System.out.println("部门:" + dept.getName() + ",雇员名:" + emp.getName()); }}
3.3.2.0.4. 测试结果
部门:销售部,雇员名:张三部门:销售部,雇员名:李四
3.3.2.1. 等值连接查询
3.4. 对象管理映射个人理解
N+1策略则是先把所有pojo对象查询出来,然后再根据外键字段去查询对象属性或者集合属性,使用association标签(property、column、select标签属性)。
N+1策略操作数据库次数为记录数+1次,等值连接策略操作数据库次数为1次。
4. MyBatis的逆向工程
MyBatis的逆向工程能自动帮开发者生成数据库表对应的pojo实体文件,自动生成映射文件自动生成表的各种(CRUD)的sql语句,但是只能做单表操作,联合查询还得开发者自己改造。使用逆向工程得先在Eclipse安装逆向工程的插件。
4.1. 插件安装步骤
4.2. eclipse逆向工程步骤
4.2.1. 新建一个普通java项目,导入mybatis.jar包和数据库驱动包
4.2.2. 配置生成文件
<?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="context1"> <!-- 注释构建 --> <commentGenerator> <!-- 去掉所有的注释 --> <property name="suppressAllComments" value="true"/> <property name="suppressDate" value="true"/> </commentGenerator> <!-- 数据库四要素 --> <jdbcConnection connectionURL="jdbc:mysql://localhost:3306/mybatis" driverClass="com.mysql.jdbc.Driver" password="root" userId="root" /> <!-- 实体类 : pojo targetPackage : 实体类生成后存放的包 targetProject : 存放的目录一般都放在 src下面 --> <javaModelGenerator targetPackage="cn.zj.mybatis.pojo" targetProject="mybatis-generator/src" /> <!-- 映射文件 --> <sqlMapGenerator targetPackage="cn.zj.mybatis.mapper" targetProject="mybatis-generator/src" /> <!-- 操作接口 type 生成映射的形式 ANNOTATEDMAPPER : 纯注解的,没有xml映射 XMLMAPPER : 生成的有xml映射文件 --> <javaClientGenerator targetPackage="cn.zj.mybatis.mapper" targetProject="mybatis-generator/src" type="XMLMAPPER" /> <!-- 要生成对应表的配置 tableName : 数据库表名 //如果下面全部是true,mybatis直接可以使用纯面向对象开发 enableCountByExample : 是否生成查询总数的 Example enableDeleteByExample : 是否生成删除的 Example enableSelectByExample : 是否生成查询集合的 Example enableUpdateByExample : 是否生成修改的 Example --> <table tableName="user" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table> <table tableName="employee" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table> <table tableName="department" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table> </context></generatorConfiguration>
4.2.3. 开始逆向工程
4.3. idea的逆向工程步骤(基于maven)
4.3.1. 新建一个maven项目,依赖引入mybatis、mysql-connector-java、以及mybatis-generator-maven-plugin插件
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> </dependencies> <build> <plugins> <!-- 逆向工程所需的插件 --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configuration> <verbose>true</verbose> <overwrite>true</overwrite> <configurationFile>src/main/resources/generatorConfig.xml</configurationFile> </configuration> </plugin> </plugins> </build>
4.3.2. 编写generatorConfig.xml配置文件
<?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> <!-- 配置驱动包的位置 --> <classPathEntry location="C:\Users\JackMi\.m2\repository\mysql\mysql-connector-java\5.1.38\mysql-connector-java-5.1.38.jar" /> <context id="context1"> <!-- 注释构建 --> <commentGenerator> <!-- 去掉所有的注释 --> <property name="suppressAllComments" value="true"/> <property name="suppressDate" value="true"/> </commentGenerator> <!-- 数据库四要素 --> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="root"/> <!-- 实体类 : pojo targetPackage : 实体类生成后存放的包 targetProject : 存放的目录一般都放在 src下面 --> <javaModelGenerator targetPackage="com.jackmi.pojo" targetProject="src/main/java"/> <!-- 映射文件 --> <sqlMapGenerator targetPackage="com.jackmi.mapper" targetProject="src/main/java"/> <!-- 操作接口 type 生成映射的形式 ANNOTATEDMAPPER : 纯注解的,没有xml映射 XMLMAPPER : 生成的有xml映射文件 --> <javaClientGenerator targetPackage="com.jackmi.mapper" targetProject="src/main/java" type="XMLMAPPER"/> <!-- 要生成对应表的配置 tableName : 数据库表名 domainObjectName:生成的pojo对象的名字 // 如果下面全部是true,mybatis直接可以使用纯面向对象开发 enableCountByExample : 是否生成查询总数的 Example enableDeleteByExample : 是否生成删除的 Example enableSelectByExample : 是否生成查询集合的 Example enableUpdateByExample : 是否生成修改的 Example --> <table tableName="user" domainObjectName="User" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table> <table tableName="employee" domainObjectName="Employee" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table> <table tableName="department" domainObjectName="Department" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table> </context></generatorConfiguration>
4.3.3. 双击mabatis-generator:generate插件
4.4. 逆向功能的缺点