MyBatis 0 为什么使用MyBatis 1 2 开发的本质是: 提高工作效率和代码性能。 MyBatis是非常优秀的持久层ORM框架,简化开发人员对关系数据库的使用。
1 2 3 4 5 6 7 8 9 10 11 12 传统JDBC步骤 传统的JDBC(Java DataBase Connectivy)步骤: 加载数据库驱动 创建并获取数据库链接 创建jdbc statement对象 设置sql语句 设置sql语句中的参数(使用preparedStatement) 通过statement执行sql并获取结果 对sql执行结果进行解析 释放资源,包括resultSet、preparedStatement、connection
JDBC 面临的问题
1 数据库连接,使用时就创建,不使用时立即释放,对数据库进行频繁连接开始和关闭,
造成数据库资源浪费,影响数据库的性能
改进: 使用数据库连接池管理 连接
2 将sql语句硬编码到java代码中,如果sql语句修改,需要重新编译java代码,不利于系统维护
改进: 将sql语句配置到xml配置文件中 , 不用重新编译Java代码
3 占位符位置和设置参数值,硬编码在java代码, 拼接Sql等问题。
改进: 将sql语句及占位符和参数全部配置到xml中。
4 resultSet中遍历结果集数据时 ,映射结果集。 获取字段,重复劳动。
1 2 3 4 5 6 MyBatis则简化了传统JDBC方法,解决了: ①频繁链接和释放数据库的浪费、 ②SQL语句与代码的高耦合、 ③参数传递麻烦、 ④结果解析麻烦。
1 MyBatis入门案例
基本操作
1 创建一个测试表, 创建测试实体类
2 用MyBatis ,导入MyBatis 需要的jar 包,或者Pom 文件,导入核心配置文件
3 测试核心配置文件中是否能得到 SqlSession
4 创建映射文件,创建接口
5 做测试
创建一个maven项目 导入Mybatis的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5 .3 </version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0 .18 </version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13 </version> <scope>compile</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2 .17 </version> </dependency>
在resources 文件夹中导入 核心配置文件和数据库配置文件 mybatis-config.xml database.properties
编写测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void count () throws Exception { SqlSession sqlSession = null ; try { String resource = "mybatis-config.xml" ; InputStream is = Resources.getResourceAsStream(resource); SqlSessionFactory factory = new SqlSessionFactoryBuilder() .build(is); sqlSession = factory.openSession(); int count = sqlSession.getMapper(SysUserMapper.class).count(); logger.debug("SysUserMapperTest count ---> " + count); } finally { sqlSession.close(); } }
针对于数据库的表 编写 操作
1 2 3 4 public interface StudentMapper { int count () ; }
编写实现
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.lpc.dao.StudentMapper" > <select id="count" resultType="int" > select count (*) from student </select> </mapper>
注意事项: 可能测试不成功,拿不到 StudentMapper.xml 映射文件,出现该问题的原因是,Maven打包的时候不打包 src/main/java下面的 xml 文件。
解决方法有2种: 第一个是 在Pom 文件中添加 以下内容,让 maven 打包的时候 打包 .xml 的文件。
1 2 3 4 5 6 7 8 9 10 11 <!--maven 默认不会 加载 .xml文件--> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**
第二种办法是 不要把 映射文件 .xml 放入 src/main/java , 而是放入 src/main/resources 下面
如: 建立一个 mapper文件夹,专门放 映射文件
1 2 3 <mappers> <mapper resource="mapper/StudentMapper.xml" /> </mappers>
2 针对于纯注解的方式
在注解上 写Sql语句
1 2 3 4 public interface StudentMapper { @Select("select count(*) from student") int count () ; }
在核心配置文件中打开 映射
1 2 3 4 <!-- 将mapper文件加入到配置文件中 --> <mappers> <mapper class ="com.lpc.dao.StudentMapper" /> </mappers>
自己完成
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 @Select("select * from tblstudent where studentId =#{studentId}") public Student selectStudentById (Integer studentId) ; @Select("select * from tblstudent where studentName LIKE concat('%',#{studentName},'%')") public List<Student> selectStudentByName (String studentName) ; @Insert("insert into tblstudent(studentName,age,studentNo,birthDay)values(#{studentName},#{age},#{studentNo},#{birthDay})") public int addStudent (Student student) ; @Update("update tblstudent set studentName = #{studentName},age = #{age} where studentId = #{studentId} ") public int updateStudent (Student student) ; @Delete("delete from tblstudent where studentId =#{studentId}") public int deleteStudent (Integer id) ;
3 优化 SqlSessionFactory 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 package cn.cvs.utils;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;import java.io.InputStream;public class MyBatisUtil { private static SqlSessionFactory factory; static { try { InputStream is = Resources.getResourceAsStream( "mybatis-config.xml" ); factory = new SqlSessionFactoryBuilder().build(is); } catch (IOException e) { throw new RuntimeException("初始化失败" , e); } } public static SqlSession createSqlSession () { return factory.openSession(false ); } public static void closeSqlSession (SqlSession sqlSession) { if (sqlSession != null ) sqlSession.close(); } }
4 三个核心对象总结 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 SqlSessionFactoryBuilder的最佳使用范围是什么? 用过即丢,负责构建SqlSessionFactory 推荐作用域范围:局部变量 SqlSessionFactory的最佳使用范围是什么? 生命周期与应用的生命周期相同 最佳作用域范围:应用的全局作用域 用 OpenSession() 创建SqlSession 实例, 默认是 false ,关闭事物提交。 OpenSession(booolean ) SqlSession的最佳使用范围是什么? 线程级 一个request请求期间 两种使用方式: 通过 namespace+id 字符串运行映射的SQL语句 基于Mapper接口方式执行SQL语句
5 做一个针对于 单表的增删改查
添加学生
SqlSession 的事物管理 1 可以直接打开事物 session=sqlSessionFactory.openSession(true);
也可以得到连接对象的时候 去提交 commit session.commit();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void test3 () { InputStream inputStream= null ; SqlSession session=null ; try { inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream); session=sqlSessionFactory.openSession(true ); StudentMapper sm= session.getMapper(StudentMapper.class); Student student=new Student(null ,"司马1" ,"男" ); sm.addStudent(student); } catch (IOException e) { e.printStackTrace(); }finally { if (session!=null ){ session.close(); } } }
6 映射文件 insert
主键的处理方式:
如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性就 OK 了。例如,如果上面的 Author 表已经在 id 列上使用了自动生成,那么语句可以修改为:
1 2 3 4 5 <insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id" > insert into Author (username,password,email,bio) values (#{username},#{password},#{email},#{bio}) </insert>
也可以 关键字 default
如果是Oracle数据库,可用序列形式
1 2 3 4 5 6 7 8 <insert id="addUser" parameterType="user" > <!-- keyProperty: 将序列号设置到user对象中,且在controller中可以使用 --> <selectKey keyProperty="id" order="BEFORE" resultType="string" > SELECT seq_changez_user.nextval id from dual </selectKey> insert into temp_changez_user (id, username) values (#{id}, #{username}) </insert>
7 数据库厂商标识 1 2 3 4 5 6 7 8 9 10 MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可: <databaseIdProvider type="DB_VENDOR" > <property name="SQL Server" value="sqlserver" /> <property name="DB2" value="db2" /> <property name="Oracle" value="oracle" /> <property name="MySQL" value="mysql" /> </databaseIdProvider>
对于不同的数据库 ,可写 两套 Sql,增加扩展性
针对插入, 主键 ,可测试。
1 2 3 4 <insert id="addStudent" parameterType="com.lpc.entity.Student" keyProperty="sid" useGeneratedKeys="true" databaseId="mysql" > insert into student (sname,sex,age) values (#{sname},#{sex},#{age}) </insert>
1 2 3 4 5 6 7 <insert id="addUser" parameterType="user" databaseId="oracle" > <selectKey keyProperty="id" order="BEFORE" resultType="_int" > SELECT seq_changez_user.nextval id from dual </selectKey> insert into temp_changez_user (id, username) values (#{id}, #{username}) </insert>
8 MyBatis 配置别名 1 每个类 配置 任意想要的名字
1 2 3 4 <typeAliases> <typeAlias alias="Author" type="domain.blog.Author" /> <typeAlias alias="blog" type="domain.blog.Blog" /> </typeAliases>
2 扫描包的形式
1 2 3 <typeAliases> <package name="domain.blog" /> </typeAliases>
MyBatis 使用的别名 在Sql 映射文件中 是不区分大小写的。
会使用 Bean 的首字母小写的非限定类名来作为它的别名
3 给特殊的类 指定别名, 如 不同包下面的相同的实体类
1 2 3 4 @Alias("author") public class Author { ... }
此外,对于常见的 Java内置类型,MyBatis框架中 也内建了 相应的别名,一般是
如 String 别名 string Boolean 别名 boolean
基本数据类型别名 byte _byte int _int
9 MyBatis 入参处理
1 单个参数 随意传
1 2 3 4 5 List<Student> findByName (String a) ;<select id="findByName" resultType="student" > select * from student where sname like concat ('%' ,#{a},'%' ) </select>
2 多个参数 都会被MyBatis重新包装成一个Map传入
1 2 3 4 5 6 7 8 9 List<Student> findByName (String a,Integer age) ;<select id="findByName" resultType="student" > select * from student where sname like concat ('%' ,#{arg0},'%' ) and age># {arg1} </select> <select id="findByName" resultType="student" > select * from student where sname like concat ('%' ,#{param1},'%' ) and age># {param2} </select>
3 命名 参数 为参数使用@Param起一个名字
1 2 3 4 5 List<Student> findByName (@Param("bb") String a,@Param("a") Integer age) ;<select id="findByName" resultType="student" > select * from student where sname like concat ('%' ,#{bb},'%' ) and age># {a} </select>
4 实体类 传入 传递POJO
5 封装为 Map参数
1 2 3 4 5 6 7 8 9 Map<String,Object> map=new HashMap<>(); map.put("bb" ,"司马" ); map.put("a" ,10 ); List<Student> findTj (Map map) ;<select id="findTj" parameterType="map" resultType="student" > select * from student where sname like concat ('%' ,#{bb},'%' ) and age># {a} </select>
10 #{} 和 ${} 的 用法 和区别
Mybatis的Mapper映射文件中,有两种方式可以引用形参变量进行取值: #{} 和 ${}
#{}: 解析为SQL时,会将形参变量的值取出,并自动给其添加引号 预编译的
1 2 3 select * from student where sname =#{name} 真实在执行的 时候 会给 name 加 单引号
${}: 解析为 SQL时,直接将值 放在那里
1 2 3 4 5 select * from student where sname = ${name}select * from student where sname = 司马会报错,要人为的加 ' ' select * from student where sname = '${name}'
两者的区别
1 2 3 4 5 6 7 8 ${}方式是将形参和 SQL语句直接拼接形成完整的SQL命令后,再进行编译,所以有Sql注入的问题。 当 username = "' OR 1=1 OR '" 传入后,${}将变量内容直接和SQL语句进行拼接,结果如下: select * from student where sname =' ' OR 1 =1 OR ' ' 所以这种会产生 Sql注入的问题。
总结
1 2 3 4 #{}方式则是先用占位符代替参数将SQL语句先进行预编译,然后再将参数中的内容替换进来。由于SQL语句已经被预编译过,其SQL意图将无法通过非法的参数内容实现更改,其参数中的内容,无法变为SQL命令的一部分。 ${}方式是将形参和 SQL语句直接拼接形成完整的SQL命令后,再进行编译,所以有Sql注入的问题。 故,#{}可以防止SQL注入而${}却不行
# 和 $ 的使用场景
1 2 3 4 5 6 7 8 模糊查询 SELECT * student user where username like '%${value}%' 推荐使用 #{} SELECT * FROM student WHERE username LIKE CONCAT ('%' , #{username}, '%' )
只能使用 $() 的场景
1 2 3 4 5 6 7 8 9 10 由于#{}会给参数内容自动加上引号,会在有些需要表示字段名、表名的场景下,SQL将无法正常执行。 order by 排序的情况 <select id="order" resultType="student" > select * from student order by #{age} desc </select> <select id="order" resultType="student" > select * from student order by ${age} desc </select>
11 resultMap 的用法
1 当 实体类 和 数据库字段不能映射时, 这类也可以用数据库 表别名 解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private Integer id;private String name;private String sex;private Integer age;<!--id 是 起的 唯一的名字 type 是实体类 类型--> <resultMap id="ss" type="s1" > <id property="id" column="sid" /> <result property="name" column="sname" /> </resultMap> <select id="findAll" resultMap="ss" > select * from student </select>
注意: 如果此处 漏写其他的 字段,和 Mybatis核心配置文件中的自动映射有关 可查看文档。。
1 2 3 4 5 <!-- 配置mybatis的log实现为LOG4J --> <settings> <setting name="logImpl" value="LOG4J" /> <setting name="autoMappingBehavior" value="NONE" /> </settings>
12 resultMap 一对一 一对多
设计数据库 表 用户表 和 订单表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 -- 用户表 CREATE TABLE `tt`.`users` ( `uid` int (11 ) NOT NULL AUTO_INCREMENT, `uname` varchar(255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `age` int (11 ) NULL DEFAULT NULL, PRIMARY KEY (`uid`) USING BTREE ) -- 订单表 CREATE TABLE `tt`.`orders` ( `oid` int (11 ) NOT NULL AUTO_INCREMENT, `oname` varchar (255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名称', `num` int (11 ) NOT NULL COMMENT '商品数量', `uid` int (11 ) NULL DEFAULT NULL COMMENT '用户id', PRIMARY KEY (`oid`) USING BTREE )
自己造 一点数据 ,分析 用户 和订单的关系 一个用户 对应 多个 订单 一个订单 对应一个用户
一对一: 订单 association 1 2 3 4 5 6 7 8 实体类设计: public class Orders { private Integer oid; private String oname; private Integer num; private Integer uid; private Users users;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 List<Orders> findAll () ;<resultMap id="oo" type="orders" > <id property="oid" column="oid" /> <!--association 用于一对一的关联 一个订单关联查询出来一个用户 --> <association property="users" column="users" javaType="com.lpc.entity.Users" > <id property="uid" column="uid" /> </association> </resultMap> <select id="findAll" resultMap="oo" > select o.*,u.* from orders o, users u where o.uid=u.uid </select>
1 2 3 4 5 6 7 8 9 10 11 <resultMap id="rm" type="com.lpc.entity.Orders" > <id property="oid" column="oid" /> <result property="oname" column="oname" /> <result property="num" column="num" /> <collection property="users" javaType="com.lpc.entity.Users" select="com.lpc.dao.UsersMapper.selectByPrimaryKey" column="uid" /> </resultMap> <select id="findById" parameterType="int" resultMap="rm" > select * from orders where oid=#{oid} </select>
一对多: 用户: collection 1 2 3 4 5 6 7 8 实体类设计: public class Users { private Integer uid; private String uname; private Integer age; private List<Orders> ordersList;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 List<Users> findAll () ;<resultMap id="uu" type="users" > <id property="uid" column="uid" /> <!--订单信息--> <collection property="ordersList" ofType="orders" > <id property="oid" column="oid" /> </collection> </resultMap> <select id="findAll" resultMap="uu" > select u.*,o.* from users u,orders o where u.uid=o.uid </select>
ofType`:指定集合里面元素的类型
多对多: 老师—职务- 职务角色表
一个老师可以担任 多个职位(如: 副校长,教务主任) 等 教务主任兼副校长
一个角色也可以被多个人担任 如有3个副校长
老师 和 职务 之间就是多对多关系
1 2 3 4 5 CREATE TABLE `teacher` ( `tid` int (11 ) NOT NULL AUTO_INCREMENT, `tname` varchar (255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , PRIMARY KEY (`tid`) USING BTREE )
1 2 3 4 5 CREATE TABLE `position` ( `pid` int (11 ) NOT NULL AUTO_INCREMENT, `pname` varchar(255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`pid`) USING BTREE )
1 2 3 4 5 6 CREATE TABLE `teacher_position` ( `id` int (11 ) NOT NULL AUTO_INCREMENT, `tid` int (11 ) NOT NULL , `pid` int (11 ) NOT NULL , PRIMARY KEY (`id`) USING BTREE )
做好三张表的数据关系
1 2 3 4 5 6 7 8 9 public class Teacher { private Integer tid; private String tname; List<Position> positions; public class Position { private Integer pid; private String pname; private List<Teacher> teachers;
多对多 即是 两个 一对多
查询 老师有多少个职位
老师的一对多
1 2 3 4 5 6 7 8 9 10 11 12 13 List<Teacher> findAll () ;<resultMap id="tt" type="teacher" autoMapping="true" > <id property="tid" column="tid" ></id> <collection property="positions" column="tid" ofType="position" > <id property="pid" column="pid" /> </collection> </resultMap> <select id="findAll" resultMap="tt" > select t.tid, t.tname,p.pid,p.pname from teacher t,teacher_position tp,position p where t.tid=tp.tid and tp.pid=p.pid </select>
13 自动映射
自动映射在 resultMap 中 讲解
设置
settings –> autoMappingBehavior
1 2 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 NONE, PARTIAL, FULL 默认值是: PARTIAL
14 延迟加载
resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。
延迟加载 要分开写:
使用association实现延迟加载
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定义 --> <resultMap type="com.mybatis.entity.Orders" id="ordersUserLazyLoading" > <!--对订单信息进行映射配置 --> <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" /> <!-- 实现对用户信息进行延迟加载 select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement) column:订单信息中关联用户信息查询的列,是user_id 关联查询的sql理解为: SELECT orders.*, (SELECT username FROM USER WHERE orders.user_id = user.id)username, (SELECT sex FROM USER WHERE orders.user_id = user.id)sex FROM orders --> <association property="user" javaType="com.mybatis.entity.User" select="findUserById" column="user_id" /> </resultMap> <!-- 根据Id查询用户,用于测试延迟加载 --> <select id="findUserById" parameterType="int" resultType="com.mybatis.entity.User" > select * from t_user where id=#{id} </select> <!-- 查询订单关联用户,用户信息延迟加载 --> <select id="findOrdersUserLazyLoading" resultMap="ordersUserLazyLoading" > select * from orders </select>
1 2 3 4 5 6 public interface OrdersCustomMapper { public List<Orders>findOrdersUserLazyLoading(); public User findUserById (int id) ; }
如果只查询 订单 相关的信息 是不会查询 用户的信息.
1 2 3 4 5 6 7 < ! < settings> < ! < setting name= "lazyLoadingEnabled" value = "true"/ > < ! < setting name= "aggressiveLazyLoading" value = "false"/ > < / settings>
使用 collection 延迟加载
用户查找多个订单
1 2 3 4 5 6 7 8 9 10 11 <resultMap type="com.yutouxiuxiu.model.Person" id="selectOrderByPersonIdLazyRM" extends="personRM" > <!– column - 主SQL语句查询的结果集的某一字段作为参数传给子SQL语句 select - 子SQL语句 –> <collection property="orderList" column="person_id" select="com.yutouxiuxiu.Orders.selectOrderByPersonId" > </collection> </resultMap> <select id="selectOrderByPersonIdLazy" parameterType="int" resultMap="selectOrderByPersonIdLazyRM" > select * from person where person_id = #{personId} </select>
1 2 3 4 <!– 一对多的时候的延迟加载的子SQL语句 –> <select id="selectOrderByPersonId" parameterType="int" resultType="Order" > select * from orders o where o.person_id = #{personId} </select>
15 resultType 和 resultMap 的区别 1 2 resultType 直接映射 POJO 中的数据, 底层也是 resultMap,用来处理简单的查询。 resultMap 要自己定义Map 映射关系,用来处理复杂的查询。
16 discriminator鉴别器 https://blog.csdn.net/qq_14810195/article/details/103307133
1 2 3 <discriminator javaType="int" column="draft"> <case value="1" resultType="DraftPost"/> </discriminator>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <resultMap id="vehicleResult" type="Vehicle" > <id property="id" column="id" /> <result property="vin" column="vin" /> <result property="year" column="year" /> <result property="make" column="make" /> <result property="model" column="model" /> <result property="color" column="color" /> <discriminator javaType="int" column="vehicle_type" > <case value="1" resultMap="carResult" /> <case value="2" resultMap="truckResult" /> <case value="3" resultMap="vanResult" /> <case value="4" resultMap="suvResult" /> </discriminator> </resultMap>
17 动态Sql 1 2 3 4 5 6 if where choose (when, otherwise) set trim (where, set) foreach
1 查询学生的 姓名 如果传入 姓名 按照姓名 模糊查询 如果没有传姓名 查询全部
if 1 2 3 4 5 6 <select id="findN" resultType="student" > select * from student <if test="name != null" > where sname like concat ('%' ,#{name},'%' ) </if > </select>
where
如果有两个值 年龄不为空 大于 年龄, 姓名不为空 ,模糊 司马 思考
1 2 3 4 5 6 7 8 9 10 11 <select id="findN1" resultType="student" > select * from student <where> <if test="name != null" > sname like concat ('%' ,#{name},'%' ) </if > <if test ="age != null" > and age>#{age} </if > </where> </select>
choose when otherwise
如果传入的年龄 大于 20 到 30 查询姓张的 传入的年龄 大于 30 查询姓王的 否则 全查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <select id="findN2" resultType="student" > select * from student <where> <choose> <when test="age > 20 and age < 30 " > sname like '%王%' </when> <when test="age > 30" > sname like '%司马%' </when> <otherwise> and 1 =1 </otherwise> </choose> </where> </select>
set 1 2 3 4 5 6 7 8 9 10 <update id="updateAuthorIfNecessary" > update Author <set> <if test="username != null" >username=#{username},</if > <if test="password != null" >password=#{password},</if > <if test="email != null" >email=#{email},</if > <if test="bio != null" >bio=#{bio}</if > </set> where id=#{id} </update>
trim 模拟其他标签
mybatis的trim 标签一般用于去除sql语句中多余的and关键字,逗号,或者给sql语句前拼接 “where“、“set“以及“values(“ 等前缀,或者添加“)“等后缀,可用于选择性插入、更新、删除或者条件查询等操作。
prefix自动在前面添加,prefixOverrides忽略最前面if的。suffixOverrides最后if的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <trim prefix="WHERE" prefixOverrides="AND" > <if test="state != null" > state = #{state} </if > <if test="title != null" > AND title like #{title} </if > <if test="author != null and author.name != null" > AND author_name like #{author.name} </if > </trim> <update id="updateS" > update student set <trim prefix="set" suffixOverrides="," > <if test="sname!=null" > sname=#{sname}, </if > <if test="sex!=null" > sex=#{sex}, </if > </trim> where sid=#{sid} </update>
foreach
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach 。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
1 2 3 4 5 6 7 8 <select id="findN3" parameterType="list" resultType="student" > select * FROM student s WHERE s.age in <foreach item="item" index="index" collection="list" open="(" separator="," close=")" > #{item} </foreach> </select>
18 MyBatis 缓存
lMyBatis系统中默认定义了两级缓存。
l一级缓存(local cache) , 即本地缓存 , 作用域默认 为sqlSession。当 Session flush 或 close 后, 该 Session 中的所有 Cache 将被清空。
l本地缓存不能被关闭 , 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域.
一级缓存失效的情况
1 2 3 4 5 6 7 8 同一次会话期间只要查询过的数据都会保存在当 前SqlSession的一个Map key:hashCode+查询的SqlId+编写的sql查询语句+参数 一级缓存失效的四种情况 1 、不同的SqlSession对应不同的一级缓存 2 、同一个SqlSession但是查询条件不同 3 、同一个SqlSession两次查询期间执行了任何一次增 删改操作 4 、同一个SqlSession两次查询期间手动清空了缓存
写案例演示
二级缓存
1 2 3 4 5 6 7 8 9 10 二级缓存(secondlevelcache),全局作用域缓存 在 namespace 级别 二级缓存默认不开启,需要手动配置 MyBatis提供二级缓存的接口以及实现,缓存实现要求 POJO实现Serializable接口 二级缓存在SqlSession关闭或提交之后才会生效 使用步骤 1 、全局配置文件中开启二级缓存 <setting name="cacheEnabled" value="true" /> 2 、需要使用二级缓存的映射文件处使用cache配置缓存 • <cache />3 、注意:POJO需要实现Serializable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void test5 () throws IOException { InputStream is=Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is); SqlSession session1=sqlSessionFactory.openSession(); SqlSession session2=sqlSessionFactory.openSession(); PositionMapper mapper1=session1.getMapper(PositionMapper.class); PositionMapper mapper2=session2.getMapper(PositionMapper.class); Position p1=mapper1.selectByPrimaryKey(1 ); System.out.println(p1); System.out.println("========================================" ); Position p2=mapper1.selectByPrimaryKey(1 ); System.out.println(p2); }
不开启提交 走 二级缓存,
开启session1 的 提交 走缓存 sqlsession2的查询,使用了缓存,缓存的命中率是0.5。
1 2 3 影响缓存命中率的因素有很多,比如缓存太小,缓存的时间太短等都会影响缓存的命中率;但从本质上讲,影响缓存命中率的因素还是取决于缓存的粒度,缓存的粒度越小缓存的命中率也就越高,目前缓存粒度最小的是对象缓存(缓存粒度:在一些软件上,缓存粒度是可以自己设置的,而缓存粒度越小读取缓存的速度也就越快,当然这要视情况而论;比如说一个缓存粒度为512kb,而你只是需要读取一个4kb的缓存内容,那么由于缓存粒度关系,它会多读出508kb的缓存); 缓存命中率与读取缓存次数有关,如果不考虑缓存的清除等因素,读取缓存的次数越多缓存命中率也就越高(这里还涉及到了权重比)
测试update操作是否会刷新该namespace下的二级缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void test6 () throws IOException { InputStream is=Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is); SqlSession session1=sqlSessionFactory.openSession(); SqlSession session2=sqlSessionFactory.openSession(); PositionMapper mapper1=session1.getMapper(PositionMapper.class); PositionMapper mapper2=session2.getMapper(PositionMapper.class); Position p1=mapper1.selectByPrimaryKey(1 ); System.out.println(p1); session1.commit(); mapper1.updateByPrimaryKey(new Position(1 ,"大校长" )); System.out.println("========================================" ); Position p2=mapper1.selectByPrimaryKey(1 ); System.out.println(p2); }
在sqlSession2更新数据库,并提交事务后,sqlsession2的StudentMapper namespace下的查询走了数据库,没有走Cache。
二级缓存 脏读 测试 测试多表关联 1对1
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 <resultMap id="rm" type="com.lpc.entity.Orders" > <id property="oid" column="oid" /> <result property="oname" column="oname" /> <result property="num" column="num" /> <association property="users" column="uid" javaType="com.lpc.entity.Users" select="com.lpc.dao.UsersMapper.selectByPrimaryKey" ></association> </resultMap> <select id="findById" resultMap="rm" > select * from orders where oid=#{oid} </select> @Test public void test7 () throws IOException { InputStream is=Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is); SqlSession session1=sqlSessionFactory.openSession(); SqlSession session2=sqlSessionFactory.openSession(); SqlSession session3=sqlSessionFactory.openSession(); OrdersMapper mapper1=session1.getMapper(OrdersMapper.class); OrdersMapper mapper2=session2.getMapper(OrdersMapper.class); UsersMapper mapper3=session3.getMapper(UsersMapper.class); Orders o1=mapper1.findById(1 ); System.out.println(o1); System.out.println("=========================" ); session1.commit(); mapper3.updateByPrimaryKey(new Users(1 ,"张安的爹" ,200 )); session3.commit(); Orders o2=mapper2.findById(1 ); System.out.println(o2); }
解决脏读的 办法
1 2 3 引入同一个缓存 空间 <cache-ref namespace="com.xmx.dao.OrdersDao" />
二级缓存 使用建议:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 是否应该使用二级缓存? 那么究竟应该不应该使用二级缓存呢?先来看一下二级缓存的注意事项: 1. 缓存是以namespace为单位的,不同namespace下的操作互不影响。2. insert,update,delete操作会清空所在namespace下的全部缓存。3. 通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace。4. 多表操作一定不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。如果你遵守二级缓存的注意事项,那么你就可以使用二级缓存。 但是,如果不能使用多表操作,二级缓存不就可以用一级缓存来替换掉吗?而且二级缓存是表级缓存,开销大,没有一级缓存直接使用 HashMap 来存储的效率更高,所以二级缓存并不推荐使用。
整合 第三方缓存 Ehcache
https://blog.csdn.net/jinhaijing/article/details/84255107
https://tech.meituan.com/2018/01/19/mybatis-cache.html
1 添加ehcache依赖
1 2 3 4 5 6 <!-- ehcache依赖 --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1 .0 </version> </dependency>
2 配置 Ehcache 的核心文件
在src/main/resources目录下新增ehcache.xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="UTF-8" ?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true" > <!-- 指定数据在磁盘中的存储位置 --> <diskStore path="D:\DEV_ENV\ehcache" /> <!-- 缓存策略 --> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" > </defaultCache> </ehcache>
1 2 3 4 5 6 7 8 9 10 其中 maxElementsInMemory:内存中最大缓存对象数 maxElementsOnDisk:硬盘中最大缓存对象数,若是0 表示无穷大 eternal:true 表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false overflowToDisk:true 表示当内存缓存的对象数目达到了 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120 秒 timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性 值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false ,该属性才有效。如果该属性值为0 ,则表示对象可以无限 期地处于空闲状态 timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EhCache将把它从缓存中清除。只有当eternal属性为false ,该属性才有 效。如果该属性值为0 ,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有 意义 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
指定
19 MyBatis Generator 逆向工程
1 导入 jar包
1 2 3 4 5 6 7 <!-- mybatis逆向工程--> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.4 .0 </version> </dependency>
2 配置核心文件 mbg.xml 放在项目的跟目录下面
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 46 47 48 <?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="simple" targetRuntime="MyBatis3Simple" > <!-- 生成没有注释的bean--> <commentGenerator> <property name="suppressAllComments" value="true" /> </commentGenerator> <!-- 配置数据库连接信息--> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://127.0.0.1:3306/tt?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull" userId="root" password="123456" > <property name="nullCatalogMeansCurrent" value="true" /> </jdbcConnection> <javaTypeResolver > <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!-- 指定javaBean生成的位置--> <javaModelGenerator targetPackage="com.lpc.entity" targetProject=".\src\main\java" > <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- 指定sql映射文件生成的位置--> <sqlMapGenerator targetPackage="com.lpc.dao" targetProject=".\src\main\java" > <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!-- 指定dao接口生成的位置,mapper接口--> <javaClientGenerator type="XMLMAPPER" targetPackage="com.lpc.dao" targetProject=".\src\main\java" > <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!-- 指定每个表的生成策略--> <table tableName="position" domainObjectName="Position" ></table> </context> </generatorConfiguration>
3 测试生成
1 2 3 4 5 6 7 8 9 10 11 12 13 public class TT { public static void main (String[] args) throws IOException, XMLParserException, InvalidConfigurationException, SQLException, InterruptedException { List<String> warnings = new ArrayList<String>(); boolean overwrite = true ; File configFile = new File("mbg.xml" ); ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration(configFile); DefaultShellCallback callback = new DefaultShellCallback(overwrite); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null ); } }
20 MyBatis 分页插件 PageHelper
pageHelper 中文 官网文档
https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
1 在 依赖中引入 pageHelper 的插件
1 2 3 4 5 6 7 <!-- 引入pageHelper分页插件--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.0 .0 </version> </dependency>
2 在核心配置文件中 配置插件。
1 2 3 4 5 6 7 8 9 <!-- plugins标签要放在typeAliases标签后面--> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor" > <!-- 分页合理化,如果pageNum > pages,就让他查询最后一页。 如果pageNum < 0 ,就查询第一页--> <property name="reasonable" value="true" /> </plugin> </plugins>
3 用PageHelper 设置
1 2 3 4 5 6 7 PageHelper.startPage(page,5 ); List<Employee> emps=employeeService.findAll(); PageInfo pageInfo=new PageInfo(emps,5 );
4 pageHelper 各个参数的意义
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 private int pageNum; private int pageSize; private int size; private int startRow; private int endRow; private long total; private int pages; private List<T> list; private int prePage; private int nextPage; private boolean isFirstPage = false ; private boolean isLastPage = false ; private boolean hasPreviousPage = false ; private boolean hasNextPage = false ; private int navigatePages; private int [] navigatepageNums; private int navigateFirstPage; private int navigateLastPage;
22 辅助 IDEA 中定制 模板