文章目录
- 一、MyBatis介绍
- 二、MyBatis原理
- 三、MyBatis的配置
- 1、配置全局配置文件
- 2、XML配置
- 1、Properties
- 2、setting
- 3、typeAliases(类型别名)
- 4、Mappers
- 3、标签详解
- 1、select标签
- 2、insert标签
- 3、update标签
- 4、delete标签
- 四、MyBatis的使用
- 1、MyBatis中接口绑定的两种实现方式
- 2、XML方式(使用步骤)
- 1、创建Mapper.java接口文件
- 2、创建Mapper.xml配置文件
- 3、配置映射路径
- 4、XML方式的开发规范
- 5、标签
- 1、select标签
- 2、resultMap和resultType的区别
- 3、insert标签
- 3、注解方式(实现步骤)
- 1、给定POJO类;
- 2、给定mapper.java接口
- 3、配置文件中指定文件映射位置
- 4、@Select注解
- 5、@Insert注解
- 6、@Update注解
- 7、@Delete注解
- 4、缓存
- 4、自定义缓存
- 5、动态SQL
- 1、if标签
- 2、where标签
- 3、 trim标签
- 4、foreach标签
- 5、模糊匹配
- 1、直接在参数上进行参数模糊匹配
- 2、mysql中提供的方法concat(,)
- 3、bind表达式
- 五、MyBatis的高级映射
- 1、订单数据模型
- 1、数据表
- 2、表与表之间的业务关系
- 3、一对一映射
- 六、MyBatis的深入
- 1、MyBatis代理详解
- 2、MyBatis的缓存机制
- 1、缓存
- 1、一级缓存
- 2、一级缓存测试
- 3、二级缓存
- 4、二级缓存原理
- 5、二级缓存使用测试
一、MyBatis介绍
- 官网中给出的解释是:
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。 - MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
二、MyBatis原理
- 如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency>
- 从XML文件中构建SqlSessionFactory。每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
- 从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。
String resource = "org/mybatis/example/mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
- 从 SqlSessionFactory 中获取 SqlSession;
try (SqlSession session = sqlSessionFactory.openSession()) { Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);}
使用和指定语句的参数和返回值相匹配的接口(比如 BlogMapper.class),现在你的代码不仅更清晰,更加类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。
例如:
try (SqlSession session = sqlSessionFactory.openSession()) { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(101);}
三、MyBatis的配置
- 除了给定MyBatis依赖之外,还要连接数据库,那就需要再导入数据库驱动:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version> </dependency>
1、配置全局配置文件
- 首先在创建的项目中找到resource;
如果没有那就创建一个新建一个包,包名设置为resource;然后鼠标右键点击依次点击如下图,将这个包设置为Resource Root;
- 在这个resource下新建一个xml类型的文件,
打开文件在MyBatis官网中找到配置信息,然后再根据需要做出一点修改;
<" /> - MyBatis的操作流程
- 配置MyBatis的全局配置文件(数据源,mapper映射)
- 创建SQLSessionFactory对象;
- 通过SQLSessionFactory创建SQLSession对象;
- 通过SQLSession来操作数据库CRUD操作;
- 关闭SQLSession资源;
2、XML配置
- MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
1、Properties
- 这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。例如:
<properties resource="db.properties"> <property name="username" value="root"/> <property name="password" value="123456"/> </properties>
- 在其中对应的属性可以用来提花为需要的动态配置的属性值,比如:
<dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource>
- 这个例子中的 username 和 password 将会由 properties 元素中设置的相应值来替换
如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
- 首先读取在 properties 元素体内指定的属性。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。
2、setting
- 这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
3、typeAliases(类型别名)
- 类型别名是Java类型的短名称,和XML配置有关,存在是在于用来减少类全限定名的冗余,在mapper.xml中,会有很多的Statement,Statement需要的parameterType指定入参类型,需要resultType执行输出参数类型,如果是指定类型需要书写全限定名,不方便开发,可以通过类型别名,较少代码冗余
<!--类型别名--> <typeAliases> <!--单个类型定义别名: type:pojo全路径 alias:别名的名称--> <typeAlias type="com.tulun.Mybatis.pojo.Student" alias="student"/> <!--批量的别名定义 package:指定包名,将包下的所有pojo类定义别名,别名是类型(首字母大写或者小写都可以)--> <package name="com.tulun.Mybatis.pojo"/> </typeAliases>
4、Mappers
- 映射器
<mappers> <!--单个文件映射:resource属性一次加载一个文件, 指定xml文件位置,通过namespace来查找mapper接口文件--> <mapper resource="mapper/StudentMapper.xml"/> <!--class方式映射:通过mapper接口映射单个文件, 遵循规则:mapper.java和mapper.xml放在同一个目录下,且命名相同--> <!--<mapper class="com.tulun.Mybatis.mapper.StudentMapper"/>--> <!--批量mapper的接口扫描规则 遵循规则:mapper.java和mapper.xml放在同一个目录下,且命名相同--> <!--<package name="com.tulun.Mybatis.mapper"/>--> </mappers>
- MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:
<!-- 使用相对于类路径的资源引用 --><mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/></mappers>
<!-- 使用完全限定资源定位符(URL) --><mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/></mappers>
<!-- 使用映射器接口实现类的完全限定类名 --><mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/></mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 --><mappers> <package name="org.mybatis.builder"/></mappers>
3、标签详解
1、select标签
<!-- 查询操作是select标签 id属性:(必填的)Statement的id是唯一标识,和接口文件中的方法名保持一致 parameterType:表示输入参数的类型(Pojo,Integer,String...)入参类型也可以是parameterMap(hashMap)两种类型二选1 resultType(必填):表示输出参数的类型(Pojo,Integer,String) 返回类型也可以是resultMap(hashMap)两种类型二选1 #{XXX}:表示占位符 XXX:接口方法中的参数名称 --> <select id="selectStudentById" parameterType="int" rresultType="student"> select * from student where SID =#{sid} </select>
2、insert标签
<!-- 插入操作是insert标签 id属性:(必填的)Statement的id是唯一标识,和接口文件中的方法名保持一致 parameterType:(可选操作)表示输入参数的类型(Pojo,Integer,String...)入参类型也可以是parameterMap(hashMap)两种类型二选1 useGeneratedKeys:开启主键回写 keyColumn:指定数据库的主键 keyProperty:主键对应的pojo类属性名 --> <insert id="testInsert" parameterMap="" useGeneratedKeys="true" keyColumn="" keyProperty=""
3、update标签
<!-- 变更操作是update标签 id属性:(必填的)Statement的id是唯一标识,和接口文件中的方法名保持一致 parameterType:(可选操作)表示输入参数的类型(Pojo,Integer,String...)入参类型也可以是parameterMap(hashMap)两种类型二选1 useGeneratedKeys:开启主键回写 keyColumn:指定数据库的主键 keyProperty:主键对应的pojo类属性名 --> <update id="testUpdate" > </update>
4、delete标签
<!-- 删除操作是delete标签 id属性:(必填的)Statement的id是唯一标识,和接口文件中的方法名保持一致 parameterType:(可选操作)表示输入参数的类型(Pojo,Integer,String...)入参类型也可以是parameterMap(hashMap)两种类型二选1 --> <delete id="testdelete"> </delete>
四、MyBatis的使用
1、MyBatis中接口绑定的两种实现方式
- 通过注解绑定:就是在接口的方法上加上@Select、@update等注解,里面包含SQL语句进行绑定;
- 通过XML里面写SQL进行绑定,需要指定xml映射文件里的namespace必须为接口的全路径
语句比较简单时,使用注解绑定,当SQL语句比较复杂是,用xml绑定,一般使用xml比较多;
2、XML方式(使用步骤)
MyBatis的强大之处在于自定义SQL语句,映射器的xml文件方式相比JDBC简单,节省代码量
1、创建Mapper.java接口文件
public interface StudentMapper { Student selectStudentById(Integer sid);}
2、创建Mapper.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"><!--根标签 namespace命令空间:给定接口文件的全路径--><mapper namespace="com.tulun.Mybatis.mapper.StudentMapper"> <select id="selectStudentById" parameterType="int" resultType="student"> select * from student where SID =#{sid} </select></mapper>
3、配置映射路径
<mappers> <mapper resource="mapper/StudentMapper.xml"/></mappers>
4、XML方式的开发规范
- 在mapper.xml中namespace命令空间等于Mapper接口的全路径名;
- Mapper.java接口中的方法名和Mapper.xml中的Statement的Id一致;
- Mapper.java接口中方法的参数类型和Mapper.xml中parameterType的指定类型一致;
- Mapper.java接口中方法的返回值类型和Mapper.xml中的resultType指定的类型一致;
5、标签
1、select标签
<!-- 当resultType无法完成某些自动字段映射时,需要resultMap进行显性映射关系配置 resultMap标签主要是用来显性指定返回值映射关系 id属性:必填,表示取名称,可以随便给定 type属性:必填,指定显性映射的Java类的全限定名 id标签:指定主键映射关系 id标签一般使用一次指定主键即可 result标签:指定非主属性映射关系,可以多次使用 property属性:指定Java类中属性名(必填) column属性:指定数据库中的属性名称(必填) jdbcType属性:指定数据库中当前属性类型(选填) javaType属性:指定Java类中属性类型(选填) typeHandler属性:类型Handler,如果是有自定义类型处理器需要在这里指定自定义类型处理器的全限定名(选填) --> <resultMap id="studentMap" type="student"> <id property="SID" column="SID" /> <result property="name" column="Sname" /> <result property="Ssex" column="Ssex" /> <result property="Sage" column="Sage" /> </resultMap>
select标签是查询操作标签id属性:(必填的)Statement的id,必填的,和接口文件中的方法名保持一致parameterType:表述输入参数的类型(String,pojo,Integer)resultType:(必填)表示输出参数的类型(String,pojo,Integer)还可以返回resultMap类型(返回的是hashmap)两种类型二选一#{XXX}:表示占位符 XXX:接口方法中的参数名称
2、resultMap和resultType的区别
- resultMap和resultType都是指定返回参数类型,类型可以是pojo类全限定名或别名,对于结果物理是单个对象还是多个对象resultMap和resultType都可以使用,值关注映射类型;
- resultType可以完成自动映射过程,但是对于字段不一致是是无法完成字段映射的,如果字段完全一致优先选取resultType;
- resultMap是可以线程完成映射过程,对于字段不一致是能够完成映射,字段不一致优先选取resultMap进行映射;
- 如果数据库属性名和Java的字段名全不一样?还能创建出映射对象嘛?不能创建出来映射对象
- 如果数据库属性名和Java的字段名只存在一个相同的,能创建出对象嘛?都能创建出对象
3、insert标签
<!-- insert标签表示插入数据 id:是Statementid,在命名空间具有唯一性,和方法名保持一致 parameterType/parameterType属性:指定入参的类型,可以是类的全限定名或者别名 flushCache属性:参数为true或者false 默认为true ,用来清除一级缓存和二级缓存 keyProperty:指定pojo类中主键属性名 useGeneratedKeys属性:默认为false,可以是false或者是true, 如果是true,会采用数据自增主键 auto_increment 完整性约束 keyColumn:表示数据库表中主键的属性名,在update和insert中有效 没有返回类型指定,如果接口方法返回为int自动给定值 --> <insert id="insertStudent" parameterType="student"> insert into student(SID,Sname,Sage,Ssex) values (#{SID},#{name},#{Sage},#{Ssex}) </insert>
3、注解方式(实现步骤)
- 注解形式就是直接将SQL写在接口方法上;
- 效率比较高;
- 但是SQL有变化时需要重新编译代码;
1、给定POJO类;
public class User { private Integer id; private String name; private String passwd; //省略getter和setter方法}
2、给定mapper.java接口
public interface UserMapper {}
3、配置文件中指定文件映射位置
<mapper class="com.tulun.Mybatis.mapper.UserMapper"/>
4、@Select注解
/** * 通过ID查询 */ @Select("select * from user where id = #{id}") public User selectUserById(Integer id);
当字段无法映射是,通过@Results注解完成显性映射;
/** * 在mybatis的早的版本:3.3.0之前版本@Results是不能共用 在mybatis的3.3.1之后的版本@Results 增加了id属性,可以通过id共用同一个Results结果 @Results和XML形式中resultMap标签类型 */ @Results(id = "userMap", value = { @Result(property = "id", column = "id", id = true), @Result(property = "name", column = "name"), @Result(property = "pass", column = "passwd") } )
5、@Insert注解
/** * 插入数据 * @Insert表示插入 * @Insert和XML中的insert标签类似 * 采用数据库自增主键时使用@Options注解,其中有useGeneratedKeys属性,设置为true */ @Options(useGeneratedKeys = true,keyColumn = "id") @Insert("insert into user(name,passwd) values(#{name},#{pass})") int insertUser(User user);
6、@Update注解
@Update的两种情况
- update操作传递一个参数;
@Update("update Studet set sex='男' where id=#{id}")public int update(int id);
这种情况只有一个参数,参数id可以不使用@Param;
- update操作传递两个及以上参数
@Update("update Student set sex=#{name} where id=#{id}")public int update(@Param("id") int id,@Param("name") String name);
这种情况有两个参数,需要使用@Param进行两个参数的区分,否则,在操作数据表时会报出SQL语句的注入风险;
7、@Delete注解
@Delete("delete from Student where id=#{id}")int deleteStudentById(Int id);
4、缓存
- MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
- 默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
- 映射语句文件中的所有select语句的结果将会被缓存;
- 缓存语句文件中所有的Insert、update和delete语句将会刷新缓存;
- 缓存会使用最近最少使用算法(LRU,least Recently Used)算法来清除不需要的缓存;
- 缓存不会定时进行刷新,(也就是说,没有刷新间隔);
- 缓存会保存列表或对象(无论查询方法返回哪种)的1024个引用;
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全的被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
- 缓存只作用于cache标签所在的映射文件中的语句,如果混合使用Java API和XML映射文件,在共用接口中的语句将不会被默认缓存,需要使用@CacheNamespaceRef注解指定缓存作用域。
- 这些属性可以通过cache元素的属性来修改,
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
- LRU – 最近最少使用:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。 - flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
- size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
- readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
4、自定义缓存
<cache type="com.domain.something.MyCustomCache"/>
- 这个示例展示了如何使用一个自定义的缓存实现。type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。
public interface Cache { String getId(); int getSize(); void putObject(Object key, Object value); Object getObject(Object key); boolean hasKey(Object key); Object removeObject(Object key); void clear();}
- 为了对你的缓存进行配置,只需要简单地在你的缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值,例如,下面的例子将在你的缓存实现上调用一个名为 setCacheFile(String file) 的方法:
<cache type="com.domain.something.MyCustomCache"> <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/></cache>
5、动态SQL
- MyBatis中特征之一支持动态SQL,以标签的形式动态编写SQL,根据业务逻辑来动态的进行SQL拼接功能;
- MyBatis提供9种动态SQL标签
trim、if、where、set、foreach、choose、when、otherwise、bind
1、if标签
- 动态SQL通常放在where子句,常用于查询、插入、更新等操作;
- 根据姓名、性别、姓名和性别的查询相应的数据
- if表达式中判断参数是否传递if test属性,该属性必填,为true或者为false,test使用OGNL表达式处理,返回为true则进入if标签中的SQL,返回为false则不会进入标签中;
<select id="selectStudentsByNameOrSex" parameterType="student" resultMap="studentMap"> select * from student where 1=1 <if test="name != null" > and Sname = #{name} </if> <if test="Ssex != null"> and Ssex = #{Ssex} </if> </select>
- 传递两个参数
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student=new Student(); student.setSname("李小龙"); student.setSsex("男"); studentMapper.selectStudentsByNameOrSex(student);
- 只传递Sname属性
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student=new Student(); student.setSname("李小龙"); studentMapper.selectStudentsByNameOrSex(student);
- 只传递Ssex属性
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student=new Student(); student.setSsex("男"); studentMapper.selectStudentsByNameOrSex(student);
- 不传递参数
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student=new Student(); studentMapper.selectStudentsByNameOrSex(student);
2、where标签
- where标签:如果该标签包含的元素有返回值,就插入一个where,如果where后面的字符串是以and或者or开头的,就将它们剔除;
<select id="selectStudentsByNameOrSex" parameterType="student" resultMap="studentMap"> select * from student <where> <if test="Sname != null" > and Sname = #{Sname} </if> <if test="Ssex != null"> and Ssex = #{Ssex} </if> </where> </select>
- 传递两个参数,只传递Sname,只传递Ssex和不传递参数
- where表达式一般和if表达式一起使用,如果条件一个都不满足,则不拼接where条件,如果有一个或者多个表达式,where会直接拼接在SQL上,并且紧随where的表达式的and/or会忽略掉;
3、 trim标签
- MyBatis提供trim标签一般适用于取出SQL语句中多余的and关键字,“逗号”,或者给SQL语句拼接“where”,“、”,“Set”,“values”等,可用于选择性插入,更新,删除或者是条件查询等操作,where和set标签都可以通过trim标签来实现;
- trim标签存在的属性
- prefix:给SQL语句拼接的前缀;
- suffix:给SQL语句拼接的后缀;
- prefixOverrides:去除SQL语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,假设该属性指定为“AND”,当SQL语句的开头为“AND”trim标签将会去除该“AND”;
- suffixOverrides:去除SQL语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定;
- trim可以和上面的where实现相同的功能
<select id="selectStudentsByNameOrSex" parameterType="student" resultMap="studentMap"> select * from student <trim prefix="where" prefixOverrides="and"> <if test="name != null"> and Sname = #{name} </if> <if test="Ssex != null"> and Ssex = #{Ssex} </if> </trim> </select>
4、foreach标签
- 适用于批量操作;
- 通过一批ID来查询用户;
List<Student> batchQueryStudent(List<Integer> ids);
select * from student where SID in(1,2,3,4,5);
<select id="batchQueryStudent" resultMap="studentMap"> select * from student where SID in <foreach collection="list" item="id" open="(" close=")" separator="," > #{id} </foreach> </select>
- foreach表达式中collection属性是必填的,指定参数入参类型列表(list)、数组(array)、HashMap(map);
- item属性:起名字,给集合中单个元素起名称;
- open属性:开始字符串;
- close属性:结束字符串;
- separator属性:分隔符;
- index属性:索引的属性名,在集合数组下值为当前的索引值;
5、模糊匹配
- 需求:查询student表中用户名包含有L的所有用户;
select * from student where Sname like “%L%”
1、直接在参数上进行参数模糊匹配
- 接口方法
List<Student> selectStudentByName(String name);
<select id="selectStudentByName" parameterType="string" resultMap="studentMap"> select * from student where Sname like #{name} </select>
2、mysql中提供的方法concat(,)
- xml配置文件
<select id="selectStudentByName" parameterType="string" resultMap="studentMap"> select * from student where Sname like concat('%',#{name},'%') </select>
3、bind表达式
- 接口方法
List<Student> selectStudentByName(Student student);
- xml配置文件
<select id="selectStudentByName" parameterType="student" resultMap="studentMap"> <bind name="ln" value="'%'+name+'%'"/> select * from student where Sname like #{ln} </select>
- bind表达式中参数必须是具有getter方法,底层是通过OGNL表达式来进行解析,该表达式通过属性的getter进行获取值
五、MyBatis的高级映射
- 本质上来说就是多个表联合进行查询的过程;
1、订单数据模型
1、数据表
- 用户表user:记录了购买商品的用户信息
- 订单表orders:记录了用户所创建的订单(购买商品的订单);
- 订单明细表orderdetail:记录了订单的详细信息即购买商品的信息;
- 商品表items:记录了商品信息;
drop table if exists `items`;CREATE TABLE `items` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL COMMENT '商品名称', `price` float(10,1) NOT NULL COMMENT '商品定价', `detail` text COMMENT '商品描述', `pic` varchar(64) DEFAULT NULL COMMENT '商品图片', `createtime` datetime NOT NULL COMMENT '生产日期', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*Table structure for table `user` */drop table if exists `user`;CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL COMMENT '用户名称', `sex` char(1) DEFAULT NULL COMMENT '性别', `address` varchar(256) DEFAULT NULL COMMENT '地址', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*Table structure for table `orders` */drop table if exists `orders`;CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '下单用户id', `number` varchar(32) NOT NULL COMMENT '订单号', `createtime` datetime NOT NULL COMMENT '创建订单时间', `note` varchar(100) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`), KEY `FK_orders_1` (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*Table structure for table `orderdetail` */drop table if exists `orderdetail`;CREATE TABLE `orderdetail` ( `id` int(11) NOT NULL AUTO_INCREMENT, `orders_id` int(11) NOT NULL COMMENT '订单id', `items_id` int(11) NOT NULL COMMENT '商品id', `items_num` int(11) DEFAULT NULL COMMENT '商品购买数量', PRIMARY KEY (`id`), KEY `FK_orderdetail_1` (`orders_id`), KEY `FK_orderdetail_2` (`items_id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
- 测试基本数据
insert into `items`(`id`,`name`,`price`,`detail`,`pic`,`createtime`) values (1,'台式机',3000.0,'该电脑质量非常好!!!!',NULL,'2015-02-03 13:22:53'),(2,'笔记本',6000.0,'笔记本性能好,质量好!!!!!',NULL,'2015-02-09 13:22:57'),(3,'背包',200.0,'名牌背包,容量大质量好!!!!',NULL,'2015-02-06 13:23:02');/*Data for the table `user` */insert into `user`(`id`,`username`,`sex`,`address`) values (1,'王五','2',NULL),(10,'张三','1','北京市'),(16,'张小明','1','陕西西安'),(22,'陈小明','1','陕西西安'),(24,'张三丰','1','陕西西安'),(25,'陈小明','1','陕西西安'),(26,'王五',NULL,NULL);/*Data for the table `orders` */insert into `orders`(`id`,`user_id`,`number`,`createtime`,`note`) values (3,1,'1000010','2015-02-04 13:22:35',NULL),(4,1,'1000011','2015-02-03 13:22:41',NULL),(5,10,'1000012','2015-02-12 16:13:23',NULL);/*Data for the table `orderdetail` */insert into `orderdetail`(`id`,`orders_id`,`items_id`,`items_num`) values (1,3,1,1),(2,3,2,3),(3,4,3,4),(4,4,2,3);
2、表与表之间的业务关系
user和orders:一对多关系
user->orders:一个用户可以创建多个订单,一对多
orders->user:一个订单只能由一个用户创建,一对一
orders和orderdetail:(一对多关系)
orders->orderdetail:一个订单可以包括多个订单明细 一对多
orderdetail->orders:一个订单明细只能包括在一个订单中 一对一
3、一对一映射
- 通过订单号来查询订单即用户信息
select * from orders o,user u where o.user_id = u.id and u.number=?
分析:通过分析,orders表示主表,user表是辅助表
在Order的pojo类上添加上User属性
public class Orders213 { private Integer id; private Integer userId; private String number; private Date createTime; private String note; //新增属性 private User213 user; //省略了getter和setter方法}
- 接口方法
Orders213 selectOrdersByNumber(String number);
- 返回结果通过resultType返回
<select id="selectOrdersByNumber" parameterType="string" resultType="com.tulun.Mybatis.pojo.Orders213"> select o.*,u.id "user.id",u.username "user.username",u.sex "user.sex",u.address "user.address" from orders o,user u where o.user_id = u.id and o.number= #{number} </select>
- 使用resultMap配置一对一映射
<resultMap id="orderUserMap" type="com.tulun.Mybatis.pojo.Orders213"> <id property="id" column="id"/> <result property="userId" column="user_id"/> <result property="number" column="number"/> <result property="createTime" column="createtime"/> <result property="note" column="note"/> <!--user属性配置--> <result property="user.id" column="u_id"/> <result property="user.username" column="u_username"/> <result property="user.sex" column="u_sex"/> <result property="user.address" column="u_address"/> </resultMap> <select id="selectOrdersByNumber" parameterType="string" resultMap="orderUserMap"> select o.*,u.id u_id,u.username u_username,u.sex u_sex,u.address u_address from orders o,user u where o.user_id = u.id and o.number= #{number} </select>
- 使用resultMap中提供的association配置一对一关系
association:用于配置关联查询的单个对象
property属性:要映射到pojo类中的对象的属性
javaType:要映射的pojo属性的全限定名
columnPrefix:针对数据库相同前缀的别名
<resultMap id="orderUserMap1" type="com.tulun.Mybatis.pojo.Orders213"> <id property="id" column="id"/> <result property="userId" column="user_id"/> <result property="number" column="number"/> <result property="createTime" column="createtime"/> <result property="note" column="note"/> <association property="user" columnPrefix="u_" javaType="com.tulun.Mybatis.pojo.User213"> <result property="id" column="id"/> <result property="username" column="username"/> <result property="sex" column="sex"/> <result property="address" column="address"/> </association> </resultMap>
优化:
extends继承自主类,resultMap使用一个已经存在的map类<resultMap id="orderUserMap2" extends="orderMap" type="com.tulun.Mybatis.pojo.Orders213"> <association property="user" columnPrefix="u_" resultMap="com.tulun.Mybatis.mapper.User213Mapper.userMap"/> </resultMap>
- 一对一映射使用resultMap下的association来配置映射关系
六、MyBatis的深入
1、MyBatis代理详解
- java中动态代理主要是JDK动态代理(接口),CGLib动态代理(类继承)
SqlSession sqlSession = sqlSessionFactory.openSession(); /** *在通过sqlSession.getMapper可以获取一个代理对象 * 对于StudentMapper并没有显性的实现该接口的实现类, * 该对象是在运行时动态产生的,该对象就是动态代理对象 * * JDK动态代理 * 1、首先存在接口(StudentMapper.java) * 2、必须存在该接口的实现类(sqlSession.selectOne) * 3、实现invocationHandler辅助类 * 4、通过Proxy类来产生代理对象 */ //单个对象原始调用方式// Student student = sqlSession.selectOne("com.tulun.Mybatis.mapper.StudentMapper.selectStudentById", 4); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student = studentMapper.selectStudentById(4); System.out.println(student);
- mybatis中代理对象的获取是通过如下方法
sqlSession.getMapper(StudentMapper.class);
- mapper接口是如何添加进去的?
通过Java代码形式来进行数据源、映射文件的配置形式如下:
Configuration configuration = new Configuration(); PooledDataSourceFactory dataSourceFactory = new PooledDataSourceFactory(); DataSource dataSource = dataSourceFactory.getDataSource(); JdbcTransactionFactory jdbcTransactionFactory = new JdbcTransactionFactory(); //设置环境 Environment environment = new Environment("test", jdbcTransactionFactory, dataSource); configuration.setEnvironment(environment); //设置mapper接口 configuration.addMapper(StudentMapper.class); new SqlSessionFactoryBuilder().build(configuration);
- 重点在于接口的额添加形式:
configuration.addMapper(StudentMapper.class);
- 在Configuration类中
public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); }
- mapper在Configuration类中没有做任何事,添加到mapperRegistry类中
public <T> void addMapper(Class<T> type) { if (type.isInterface()) {//只允许添加接口 if (hasMapper(type)) {//不允许重复添加 throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { knownMappers.put(type, new MapperProxyFactory<T>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } }
- 执行的configuration.addMapper(StudentMapper.class),实际上最终被放入HashMap中,其命名knownMappers,它是mapperRegistry类中的私有属性,是一个HashMap对象,key是接口class对象,value是MapperProxyFactory的对象实例;
- 通过getMapper获取代理对象
DefaultSqlSession类:
public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
- configuration类中存放所有的mybatis全局配置信息从参数上可以知道class类型
configuration类:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
- configuration类中getmapper调用了mapperRegistry.getMapper,mapperRegistry中存放一个HashMap
mapperRegistry类:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
- 调用mapperRegistry类中的getMapper方法,该方法中会到hashmap中通过类名获取对应的value值,是MapperProxyFactory对象,然后调用newInstance方法,该方法是创建了一个对象
public class MapperProxyFactory<T> {//映射器代理工厂 private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { //使用的是JDK自带的动态代理对象生成代理类对象 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }}
public class MapperProxy<T> implements InvocationHandler, Serializable {//实现了InvocationHandler接口 @Override //代理之后,所有的mapper的方法调用,都会调用这个invoke方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); //该位置就调用了原始的调用方法:SQLSession.selectOne } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } final MapperMethod mapperMethod = cachedMapperMethod(method); //执行CURD操作 return mapperMethod.execute(sqlSession, args); }}
- mybatis中getmapper的代理对象的获取,使用的是JDK动态代理,在MapperRegistry类中的HashMap中存放了所有的mapper接口,key是接口的类名信息,value是MapperProxyFactory类型实例,getmapper操作会是当前对象调用newInstance操作,该操作会调用Proxy.newProxyInstance核心在于调用InvocationHandler接口,实现了invoke方法,除了调用原始的接口调用,而且还对调用进行增强,进行了方法缓存,且最终会执行增删改查操作;
- 总结Mapper接口方法执行的几个点:
- Mapper接口初始在SQLSessionFactory注册的
- Mapper接口注册在MapperRegistry类的HashMap中,key是mapper的接口类名,value是创建当前代理工厂
- Mapper注册之后,可以从过SQLSession来获取get对象
- SQLSession.getMapper运用了JDK动态代理,产生了目标Mapper接口的代理对象,动态代理的代理类是MapperProxy,实现了增删改查调用
2、MyBatis的缓存机制
1、缓存
- mybatis中提供查询缓存,用于减轻数据库的压力,提高数据库的性能mybatis提了了一级缓存和二级缓存
1、一级缓存
- 一级缓存是SQLSession界别缓存,在操作数据库时都需要构造SQLSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的SQLSession之间的缓存数据区域是互不影响的
- 一级缓存的作用于是在同一个SQLSession,在同一个SQLSession中执行相同的两次SQL,第一次执行完毕在后会将数据写到缓存中,第二次从缓存中进行后去就不在数据库中查询,从而提高了效率,
当一个SQLSession结束后该SQLSession中的一级缓存也就不存在了,mybatis默认开启一级缓存
- 每个SqlSession中持有一个执行器Executor,每个执行器中有一个Local Cache,当用户发起查询时,mybatis根据当前执行的语句生成mapperStatement,在Local Cache中进行查询,如果缓存命中的话,直接返回给用户,如果没有命中,查询数据库,结果写入Local Cache中,最后返回给用户
- 通过ID查询用户信息
- 缓存生效失效时机:
如果是连续的查询同一个数据操作,在第一次查询之后,后续查询都可以命中缓存
如果在查询之后,紧接着是进行变更操作,就会导致缓存失效
2、一级缓存测试
- mybatis是默认支持一级缓存,不需要做其他配置;
- 测试1:同一个SQLSession下连续进行查询操作
//同一个SQLSession连续进行查询操作 StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); //第一次查询操作 System.out.println("第一次查询开始......"); Student student = studentMapper.selectStudentById(4); System.out.println("第一次查询结束......"); System.out.println("第二次查询开始......"); Student student1 = studentMapper.selectStudentById(4); System.out.println("第二次查询结束......");
- 通过执行结果可知:第一次查询会查询数据库,第二次查询操作是没有查询SQL,即通过缓存查询到结果返回
- 测试2:第一次查询结束后,对数据进行变更操作,再次执行查询操作
//同一个SQLSession连续进行查询操作 StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); //第一次查询操作 System.out.println("第一次查询开始......"); Student student = studentMapper.selectStudentById(4); System.out.println("第一次查询结束......"); //对数据进行变更操作 System.out.println("变更操作开始...."); studentMapper.updateNameById(4,"test"); sqlSession.commit(); System.out.println("变更操作结束...."); //第二次查询 System.out.println("第二次查询开始......"); Student student1 = studentMapper.selectStudentById(4); System.out.println("第二次查询结束......");
- 在查询结束后,如果对于数据进行变更操作,会删除掉缓存,导致第二次查询依然需要进入到数据库
- case3:不同的SQLSession下同一个操作是否会命中缓存
SqlSession sqlSession = sqlSessionFactory.openSession(); //分别数据不同的SQLSession是否会命中缓存 StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); //第一次查询操作 System.out.println("第一次查询开始......"); Student student = studentMapper.selectStudentById(4); System.out.println("第一次查询结束......"); SqlSession sqlSession1 = sqlSessionFactory.openSession(); StudentMapper studentMapper1 = sqlSession1.getMapper(StudentMapper.class); //第二次查询 System.out.println("第二次查询开始......"); Student student1 = studentMapper1.selectStudentById(4); System.out.println("第二次查询结束......");
- 不同的SQLSession的一级缓存是无法共享
3、二级缓存
- 二级缓存是mapper级别缓存,多个SQLSession去操作同一个mapper的SQL语句,多个SQLSession操作都会存在二级缓存中,多个SQLSession共用二级缓存,二级缓存是跨SQLSession
- 二级缓存是多个SQLSession共享的,作用域是mapper下的同一个namespace。不同的SQLSession两次执行相同的namespace下的SQL最终能获取相同的SQL语句结果
- mybatis默认是没有开启二级缓存的,需要在全局配置文件中配置开启二级缓存
- mybatis中二级缓存是mapper级别的缓存,默认是关闭。对于一个mapper下的不同的SQLSession可以共享二级缓存,不同的mapper是相互隔离的,二级缓存的特点是需要打开二级缓存的配置,并且需要映射的java类需要实现序列化
4、二级缓存原理
二级缓存和一级缓存的区别:
二级缓存范围更大,多个SQLSession可以共享一个mapper级别的二级缓存,数据类型依然是HashMap来存储二级缓存内容,mapper是按照namespace划分,如果namespace相同则使用同一个二级缓存,
一级缓存范围会更小,是一个SQLSession级别
- 二级缓存使用步骤
- 在mybatis的全局配置文件中开启二级缓存
<settings> <!--cacheEnabled:开启mybatis的二级缓存--> <setting name="cacheEnabled" value=" true"/> </settings>
2. 将映射的pojo类实现序列化
public class Student implements Serializable
3. 在mapper配置文件中使用cache标签
<!-- cache和二级缓存相关标签 eviction属性:代表缓存的回收策略,mybatis提供了回收策略如下: LRU:最近最少使用,对于最长时间不用的对象进行回收 FIFO:先进先出,按照帝乡进入缓存的顺序来进行回收 SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象 WEAK:弱引用,基于垃圾回收器状态和弱引用规则的对象 flushInterval属性:刷新间隔时间,单位是毫秒 size属性:引用数目,代表缓存可以存储最多多少个对象 readOnly属性:只读,意味着缓存数据只能读取不能修改 --> <cache eviction="FIFO" flushInterval="1000" size="1024" readOnly="false"/>
5、二级缓存使用测试
//二级缓存的测试 @Test public void testCache2() { //不同的SQLSession会话进行相同的SQL查询操作 //SQLSession1实例 SqlSession sqlSession = sessionFactory.openSession(); Student23Mapper student23Mapper = sqlSession.getMapper(Student23Mapper.class); //SQLSession2实例 SqlSession sqlSession1 = sessionFactory.openSession(); Student23Mapper student23Mapper1 = sqlSession1.getMapper(Student23Mapper.class); //sqlsession实例3 SqlSession sqlSession2 = sessionFactory.openSession(); Student23Mapper student23Mapper2 = sqlSession2.getMapper(Student23Mapper.class); //第一次查询id为1的用户 Student23 student23 = student23Mapper.selectStudentByUid(1L); System.out.println(student23); //这里执行关闭操作,将SQLSession中的数据写入到二级缓存区域 sqlSession.close(); //第二次查询id为1的用户 Student23 student24 = student23Mapper1.selectStudentByUid(1L); System.out.println(student24); sqlSession1.close(); }
- 表明不同的SQLSession可以共享一个二级缓存
- 底层:二级缓存是一个Cache接口的实现类,一级缓存是一个localCache是一个HashMap的属性
执行过程先经过二级缓存,在二级缓存未命中时才会走一级缓存