0. 简介
本文用于记录本人的MyBatis学习内容,只编写了部分个人认为比较常用到的一些知识点,参考资料分别源自以下:
MyBatis官方文档
C语言中文网编写资料
B站狂神说Java——MyBatis相关视频
可能有部分理解不到位的地方,不喜勿喷。
1. 什么是MyBatis:
- MyBatis是一个开源,轻量级的数据持久化框架,是JDBC和Hibernat的替代方案。
- MyBatis内部封装了JDBC,简化了加载驱动,创建链接,创建statement等繁杂的过程,开发者只需要关注SQL语句本身。
- 数据持久化是指将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中数据模型的统称。我的理解是将实体类中的信息映射到数据库中,以及将数据库中的信息以实体类的形式存储的过程称为数据持久化。
- 实现数据持久化的技术:ORM(Object Relational Mapping,对象关系映射),它在对象模型和关系型数据库之间建立起对应关系,并且提供了一种机制,通过JavaBean对象去操作数据库中的数据。
优点:
- SQL写在XML中,和程序逻辑代码分离,降低耦合度,便于统一管理和优化,提高了代码的可重用性;
- 提供XML标签,支持编写动态SQL语句;
- 提供映射标签,支持对象与数据库的ORM字段关系映射;
2. MyBatis下载:Maven项目导入包
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
</dependencies>
3. MyBatis如何使用:
-
创建样例数据库:
Create Table `website`( `id` int(11) not null auto_increment, `name` varchar(20) not null, `url` varchar(30) default '', `age` tinyint(3) unsigned not null, `country` char(3) not null dafault '', `createTime` timestamp null default null, primary key(`id`) )engine=InnoDB default charset=utf8 collate=utf8_unicode_ci;
-
创建对应的实体类(也称为"持久化类"):
public class Website{ private int id; private String name; private String url; private int age; private String country; private Date createTime; //这里的Date是java自带的工具类中的Date,而不是sql包下的Date //尽量保持属性名与数据库中的字段名一致,否则会出现部分异常 //省略gettter和setter方法,省略全参数构造和无参构造 //此处省略toString方法的重写 }
-
创建Dao层操作接口WebsiteDao.java,及相关映射文件WebsiteDaoMapper.xml,如下:
public interface WebsiteDao{ //这里暂时写WebsiteDao方便理解,后面可以改为WebsiteMapper //往数据库中添加一个新的记录 public int addWebsite(Website website); //查询所有的网站信息 public List<Website> selectAllWebsite(); }
<?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="xxx.xxx.xxx.WebsiteDao"> <!--这里的xxx是指你写的WebsiteDao的包名--> <insert id="addWebsite" paramterType="xxx.xxx.xxx.Website"> insert into website(name,url,age,country,createTime) values(#{name},#{url},#{age},#{country},#{createTime}) </insert> <!--insert是一个添加标签,表示其中的sql语句功能是添加记录--> <!--id指的是对应接口中的方法名--> <!--#{}算是一个固定格式,在MyBatis中取得paramterType中的参数--> <select id="selectAllWebsite" resultType="xxx.xxx.xxx.Website"> select * from website </select> <!---resultType是指结果返回形式-> </mapper>
-
创建配置文件并绑定mapper:(mybatis-confg.xml)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--这里是配置了MyBatis的日志,可以不用理--> <settings> <setting name="logImpl" value="LOG4J" /> </settings> <environments default="development"> <!--可以通过设置多个environment,通过更改default来设置--> <transactionManager type="JDBC"/> <!--设置事务管理--> <dataSource type="POLLED"> <!--设置数据源,也可以说设置连接池--> <property name="driver" value="……" /> <property name="url" value="……" /> <property name="username" value="……" /> <property name="password" valye="……" /> <!--设置连接数据库需要的信息--> </dataSource> </environments> <mappers> <mapper resource="xxx/xxx/xxx/WebsiteDaoMapper.xml" /> <!--完成绑定--> </mappers> </configuration>
-
编写测试代码:
public class Test{ public static void main(String[] args){ InputStream config = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config); SqlSession session = ssf.openSession(); //这一部分代码的可重用性很高,建议用工具类写,同时每次测试完记得关闭资源 WebsiteDao websiteDao = session.getMapper(WebsiteDao.class); Website website = new Website("xxx","xxx",1,"xxx",new Date(/*获得系统 时间的方法*/)); websiteDao.addWebsite(website); List<Website> websiteList = websiteDao.selectAllWebsite(); session.comit(); //这里可以通过日志查看数据库操作的结果 session.close(); } }
-
基本流程如上图所示,MyBatis的作用在本案例中,体现在简化了SQL的执行准备,如果是使用常规的JDBC的话,需要连接数据库,新建statement等多余代码,使用了MyBatis,通过配置文件使得代码工作更加关注SQL语句本身,同时使用也更加地方便。
-
可能遇到的问题:
- database配置写错
- MyBatis配置文件中,相关包名异常
- database连接需要设置时区
- 静态资源过滤(非resource包下的xml资源需要过滤到tar项目中)
- ResultType中,如果设置的类名的属性字段与数据库的字段名不一致则无法注入
4. MyBatis核心对象:核心接口和类,MyBatis核心配置文件,SQL映射文件
5. 核心接口和类:SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession;
-
每个MyBatis应用程序都以一个SqlSessionFactory对象的实例为核心,首先获取SqlSessionFactoryBuilder对象,可以根据XML配置文件或者Configuration类的实例构建对象,然后通过build()方法来获取SqlSessioinFactory对象,最后通过SqlSessionFactory的openSession()方法获得目标SqlSession。
InputStream config = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config); SqlSession session = ssf.openSession();
-
SqlSessionFactoryBuilder:这个类的作用是为了创建SqlSessionFactory的,一旦SqlSessionFactory被创建了,那么这个类就没有了存在的价值,应该被销毁,所以SqlSessionFactoryBuilder最好的作用域就是方法体内,用完即销毁,可以通过工具类实现;
-
SqlSessionFactory:SqlSessionFactory一旦被创建,就会存在于程序的整个生命周期,也就是只要程序不结束,它就会一直存在着。所以我们要做的是不要重复地去创建SqlSessionFactory,因此最好的实现是单例模式;
-
SqlSession:SqlSession实例是不能共享的,并且不是线程安全的,所以它最好的作用域是方法体内,或者是一次请求时,即method或者request,也就是说收到一次Http请求时,就应该创建一个SqlSession实例,然后在结束的时候再把它关闭,调用close()方法;
public MyBatisUtil{ private volatile static SqlSessionFactory sqlSessionFactory; private MyBatisUtil(){} public static SqlSessionFactory getSqlSessionFactory(){ if(sqlSessionFactory==null){ synchronized(MyBatisUtil.class){ if(sqlSessionFactory==null){ InputStream config = Resources.getResourceAsStream("mybatis-config.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(config); } } } return sqlSessionFactory; } }
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession(); try{ //相关操作 }finally{ sqlSession.close(); } //这里使用try并不是因为有异常需要捕获,而是为了使用finally保证sqlSession在使用完后一定会关闭
6. MyBatis核心配置文件:
-
properties标签:
-
引入外部配置文件,一般设置数据库的相关配置;
driver=……………… url=…………………… username=…………………… password=…………………… <!--文件名为database.properties,用来设置database的连接相关参数--> <!--在mybatis_config文件中可以直接使用${}的形式进行引用-->
<properties resource="xxx.xxx.xxx.database.properties" />
-
properties子元素配置:通过properties子元素property设置数据库的相关内容,然后再在下面的environments中引用,其实根本上和上面的用法差不多;
<properties> <property name="username" value="……" /> <property name="password" value="……" /> </properties>
<environment id="default"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value=${driver}/> …………………… …………………… </dataSource> </environment>
-
如果有相同的变量名,properties优先使用外部引入的变量中的值,即内部定义的外部同名变量会被覆盖;
-
{}和${}的区别:
- ${}是字符串替换,即直接会被替换成目标的值;#{}是预编译处理,会对传进来的数据加一个双引号"";
- 在MyBatis中,处理#{}时会将其替换成 ? ,使用PreparedStatement的set()方法来赋值,防止SQL注入;而处理 时 则 是 直 接 替 换 成 变 量 的 值 , 如 果 在 S Q L 中 使 用 {}时则是直接替换成变量的值,如果在SQL中使用 时则是直接替换成变量的值,如果在SQL中使用{}则有SQL注入的风险。
-
-
typeAliases标签:设置类型别名
-
设置类别名,意在降低冗余的全限定(指加上包名)类名书写;
<typeAliases> <typeAliases alias="Author" type="xxx.xxx.xxx.Author" /> ………………………… ………………………… </typeAliases>
-
也可以指定一个包名,MyBatis会在包名下面搜索需要的JavaBean,相比较上面的方式,如果JavaBean都写在同一个目录下,这种方式更加省力气;
<typeAliases> <package name="xxx.xxx.pojo" /> </typeAliases>
每一个在包pojo中的JavaBean,在没有注解的情况下,会使用Bean的首字母小写的非限定类名作为别名,若有注解则以注解为准;
@Alias("author") public class Author{ …………………… }
-
-
settings标签:是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
可能用到的属性:
属性 描述 有效值 默认值 cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存,即设置二级缓存的开关 true|false true lazyLoadingEnabled 延迟加载的全局开关(不清楚作用) true|false false logImpl 指定MyBatis所用日志的具体实现,未指定时自动查找,找不到则不设置 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设 如何设置日志:
-
常用的日志有:LOG4J和STDOUT_LOGGING。如果使用标准输出的日志不需要额外设置,如果使用LOG4J则需要进行一些额外的操作;
-
导入依赖:
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
通过配置文件来进行配置log4j日志设置
# log4j.properties # 全局日志配置 # 将等级为ERROR的日志信息输出到stdout,stdout的定义在下面,这里也可以选择输出到文件 log4j.rootLogger=ERROR, stdout # MyBatis 日志配置 log4j.logger.org.mybatis.example.BlogMapper=TRACE # 控制台输出 log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n # 为了实现更细粒度的日志输出,你也可以只打印特定语句的日志。以下配置将只打印语句 selectBlog 的日志: log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE # 或者,你也可以打印一组映射器的日志,只需要打开映射器所在的包的日志功能即可: log4j.logger.org.mybatis.example=TRACE # 设置日志的输出等级 log4j.logger.org.mybatis.example=DEBUG
-
-
mappers标签:
-
绑定相关Mapper的xml文件
<mappers> <mapper resource="xxx/xxx/xxx/WebsiteDapMapper.xml" /> …………………… …………………… </mappers>
-
直接绑定类文件:
<mappers> <mapper class="xxx.xxx.xxx.WebsiteDao" /> …………………… …………………… </mappers>
-
将包内的映射器接口实现全部注册为映射器,即自动扫描:
<mappers> <package name="xxx.xxx.mapper" /> </mappers>
-
尽量保证接口和mapper配置文件名保持一致,且在同一个包下
-
7. SQL映射文件
-
select标签:代表查询语句,如下
<select id="方法名" paramterType="参数类型" resultType="返回类型"> <!--假设这里的参数是int类型,变量名为id,则有--> select * from xxx where id = #{id} </select>
这个语句代表,从xxx数据库搜索对应id为传来参数id的记录的所有信息,这里的#{id}是告诉MyBatis创建一个PreparedStatement,然后使用"?"来代替原来的为止,通过set()方法设置参数,以此防止SQL注入;
设置返回属性时,出了可以设置resultType,还可以设置resultMap,但只能选择其中一种,resultMap常用于多表查询的情况;
在设置了resultType的情况下,MyBatis会在幕后自动创建一个ResultMap,再根据属性名来映射列到JavaBean的属性上,所以最好将实体类的属性名与数据库的字段名保持一致,如果不一致,可以通过select设置别名完成匹配;
使用了resultMap的情况如下:
<select id="方法名" paramterType="参数类型" resultMap="resultMap标识"> select * from xxx where id = #{id} </select> <resultMap id="resultMap标识" Type="返回类型,一般是JavaBean的类型"> <result property="id" column="user_id" /> <result property="username" column="user_name" /> <result property="password" column="user_password" /> </resultMap>
比较复杂的查询可以使用resultMap一一映射查询结果;
如果需要传递多个参数,可以使用Map来接收,或者通过注解**@Param("……")**,这样在xml文件中就不用选择参数接收属性,直接编写SQL语句即可;
-
insert标签,update标签,delete标签:增改删标签,常用属性为id;
-
sql标签,可用来定义可重用sql片段,一般不怎么用,可以是不完整的sql代码
-
KaTeX parse error: Expected 'EOF', got '#' at position 4: {}和#̲{}的应用:#{}可以用来防止…{}在SQL中也有一定的作用。
-
如,当我们需要使用order by子句时,可以通过设置SQL语句为" ORDER BY ${columName} ",这样子就可以实现在SQL中直接插入一个不转义的字符串;
-
如,当我们需要根据数据库的某些字段名来搜索记录的时候,我们可以通过传入字段名,以及目标值,来达到我们的目的,而不用重复地写多个SQL,具体代码如下:
@Select("select * from user where ${columnName} = #{value}") public List<User> getUserListBy (@Param("columnName") String columnName,@Param("value") String value); //通过这种方式,同时注入字段名和字段值,减少了重复的代码 //这里使用了注解实现Mapper
-
8. resultMap中,collection和association属性的使用
-
为什么要使用association和collection?
像Java系统中自定义好的类,可以通过result属性来进行注入,而对于我们自己定义的类,只能通过使用association属性来设置;
-
association主要用来解决一对一的关联查询,即像每个学生对应绑定一个学生号一样,这里用学生号和学生做例子,代码如下:
create table `student`( `id` …………………… , `name` …………………… , `sex` …………………… , `cardId` …………………… , …………………… )…………………… create teble `studentcard`( `id` …………………… , `studentId` …………………… , `startDate` ……………………, `endDate` …………………… , …………………… )……………………
public class Student{ private int id; private String name; private int sex; private StudentCard studentCard; } public class StudentCard{ private int id; private int studentId; private Date startDate; private Date endDate; }
针对于Student的查询有:
<mapper namespace="xxx.xxx.xxx.StudentMapper"> <select id = "selectStudentById" resultMap = "Student"> select student.*,studentcard.* from student,studentcard where student.id = #{id} and student.id = studentId </select> <resultMap id="Student" type="xxx.xxx.xxx.Student"> <id property="id" column="student.id" /> <result property="name" column="name" /> <result property="sex" column="sex" /> <association property="studentCard" javaType="xxx.xxx.xxx.StudentCard"> <id property="id" column="studentcard.id" /> <result property="studentId" column="studentId" /> <result property="startDate" column="startDate" /> <result property="endDate" column="endDate" /> </association> </resultMap> </mapper>
这里的javaType表示association要映射到的类,这种一对一的关联查询,通过这样写SQL可以实现一次查询便获取所需要的所有值,方便理解,也有另外一种书写方式,但是我理解不来,所以就用这种方式写;
-
collection主要用来解决一对多的关联查询,即,如果自定义类中存在某个属性是集合的话,则需要用到collection来解决,以用户和订单为例,代码如下;
create table `order`( `id` …………………… , `ordernum` …………………… , `userId` …………………… , …………………… )…………………… create table `user`( `id` …………………… , `name` …………………… , `pwd` …………………… , …………………… )……………………
public class User{ private int id; private String name; private String pwd; private List<Order> orderList; } public class Order{ private int id; private int ordernum; }
针对于User的查询有:
<mapper namespace="xxx.xxx.xxx.UserMapper"> <select id="selectUserById" paramterType="int" resultMap="User"> select user.*,order.id,order.ordernum from user,order where user.id = #{id} and order.id = #{id} </select> <resultMap id="User" type="xxx.xxx.xxx.User"> <id property="id" column="user.id" /> <result property="name" column="name" /> <result property="pwd" column="pwd" /> <collection property="orderList" ofType="xxx.xxx.xxx.Order"> <id property="id" column="order.id" /> <result property="ordernum" column="ordernum" /> </collection> </resultMap> </mapper>
这里的ofType是指集合中要存放的对象类型,然后这里的SQL写的其实有一点问题但是懒得改了。通过这种一次查询玩需要的所有信息的方式可以简洁明了地完成一对多的关联查询的问题。
9. 常用简单注解,以及实现SQL的注解
- @Insert:实现新增
- @Select:实现查询
- @Uodate:实现更新
- @Delete:实现删除
- @Param:映射多个参数(即,用于传递参数)
10. 动态SQL的书写:使用相关标签的嵌套完成动态SQL的实现
-
if
<if test="判断条件"> SQL语句 </if> <!--sql语句只有在判断条件为true时,才会被执行--> <select id="……" resultMap="……"> select xxx,xxx,xxx from xxx <if test="name!=null"> <!--这里的name作为判断条件并不需要加上#{}--> where name like #{name} </if> </select>
-
choose(when,otherwise):相当于Java中的switch
<choose> <when test="判断条件1"> SQL语句 </when> <when test="判断条件2"> SQL语句 </when> <when test="判断条件3"> SQL语句 </when> <otherwise> SQL语句 </otherwise> </choose> <!--在这里面,sql执行只会走其中的一种情况,一匹配完执行完SQL后,就会跳出choose--> <select id="xxx" parameterType="xxx" resultMap="xxx"> select xxx,xxx,xxx from xxx where 1=1 <choose> <when test="name!=null and name!=``"> and name like concat(`%`,#{name},`%`) </when> <when test="url!=null and url!=``"> and url like concat(`%`,#{url},`%`) </when> <otherwise> and age is not null </otherwise> </choose> </select> <!--这里test里面的逻辑通过and来连接,如果是或者的关系,则用or来连接-->
-
trim(where,set):上面的例子中,如果没有了 “1=1” 的这个前置判断条件,则会出现 “where and” 的情况,这样明显是错误的,我们可以通过设置where标签来避免这种错误;
<select id="xxx" paramterType="xxx" resultMap="xxx"> select xxx,xxx,xxx from xxx <where> <if test="name!=null and name!=``"> and name like concat(`%`,#{name},`%`) </if> <if test="url!=null and url!=``"> and url like concat(`%`,#{url},`%`) </if> </where> </select>
通过这种方式,可以解决以上提出的问题。where标签会在生成PreparedStatement的时候自动加上where,同时判断,如果其后跟着的条件是第一个,则会自动把or或者and省去。set标签则是设计来应对update的情况,剔除追加到条件末尾多余的逗号,示例如下:
<update id="xxx" paramterTyp="xxx"> <!--常规的update语句更新方法--> <!--update xxx set xxx=#{xxx},xxx=#{xxx},xxx=#{xxx} where id=#{id}--> <!--使用了set标签的情况--> update xxx <set> <if test="name!=null"> name=#{name}, </if> <if test="url!=null"> url=#{url} </if> </set> <!--set标签会自动填补上set关键字,同时如果有多个update字段也会自动填补间隙的","--> where id=#{id} </update>
set标签和where标签都只是trim标签的一种应用,使用trim标签可以定制自己需要的特殊标签,trim标签的格式如下:
<trim prefix="前缀" suffix="后缀" prefixOverrides="忽略前缀字符" suffixOverrides="忽略后缀字符"> SQL语句 </trim> <!-- prefix:给SQL语句拼接上一个前缀 suffix:给SQL语句拼接上一个后缀 prefixOverrides:去除SQL语句前面的关键字或字符 suffixOverrides:去除SQL语句后面的关键字或字符 -->
-
foreach:foreach标签用来对集合进行遍历(尤其是在构建IN条件语句的时候),如:
<foreach item="item" index="index" collection="list" open="(" separator="," close=")"> <!--这里应该写集合中传过来的对象的值--> <!--一般传int或者String类型的所以直接item就可以了--> item </foreach> <!-- item:用来表示处于当前遍历顺序下的元素 index:表示当前的遍历位置 list:外部提供的遍历的集合 open:表示该部分语句被编译后以什么符号开始 separator:表示该部分语句被编译后,被遍历的元素之间用什么分割 close:表示该部分语句被编译后以什么符号结束 效果:(xxx,xxx,xxx) 因此一般搭配IN条件语句使用 -->
11. MyBatis缓存
-
什么是缓存
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,而是从缓存中查询,从而提高查询效率。
- 可以减少和数据库的交互次数,减少系统开销,提高系统效率
- MyBatis系统中默认定义了两级缓存,一级缓存和二级缓存。默认情况下只有一级缓存开启(SqlSession级别,也称为本地缓存),二级缓存需要手动开启和配置,它是基于namespace级别的缓存。同时为了提高扩展性,MyBatis定义了缓存接口,我们可以通过实现Cache接口来自定义二级缓存。
- 缓存的清除策略:
- LRU——最少使用原则,默认
- FIFO——先进先出原则
-
一级缓存
-
二级缓存:想要开启二级缓存,除了需要在settings中开启全局缓存,还需要在SQL映射文件中添加以下代码:
<cache evition="FIFO" flushInterval="60000" <!--刷新间隔时间--> size="512" <!--最多引用数目--> readOnly="true" />
作用如下:
- 映射语句文件中的所有select语句的结果将会被缓存
- 映射语句文件中的所有insert,update和delete语句会刷新缓存
- 使用LRU清除策略
- 缓存不会定时进行刷新
- 不加限定的情况下,缓存会保存列表或对象的1024个引用
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改
一个会话查询一条数据,这个数据会被放在当前会话的一级缓存中;如果当前会话关闭了,且配置的对应的映射器中开启了二级缓存,此时数据会被移交到二级缓存中,新的会话查询信息就可以从二级缓存中获取内容。 使用二级缓存的时候可能会因为没有序列化而报错,注意实现序列化接口即可。
-
缓存查询原理
- 优先查询二级缓存中是否存在缓存记录
- 如果二级缓存中没有缓存记录,再从一级缓存中查询
- 如果一级缓存也查不到,那么向数据库发起查询请求
-
Ehcache自定义缓存
如果要使用自定义缓存的话,需要先引入依赖,即Ehcache的包,然后在配置文件上加入:
<cache type="org.mybatis.cache.ehcache.EhcacheCache" />
再配置自定义的xml配置文件,即ehcache;
如果想要实现自定义缓存,可以通过自定义类,实现Cache接口然后将type中的字段改成自己定义的类的地址即可。
-
也可以使用Redis做缓存,这里不介绍。(好像正式开发Redis用的比较多,MyBatis自带的不怎么用)