一、Mybatis概述
Mybatis是一款优秀的持久层框架。
支持自定义SQL、存储过程以及高级映射。
免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。
可以通过简单的XML或者注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Object,普通老式Java对象)为数据库中的记录。
二、项目构建
Mybatis的依赖需自行在Meven仓库下载。
1.编写Mybatis的核心配置文件
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的,Mybatis核心配置文件就是来用帮助我们构建SqlSessionFactory的。
<?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>
<!--【外部资源】
-引入配置连接字段的文件 db.properties
-->
<properties resource="db.properties"/>
<!--【设置】
-添加日志文件,查看执行的SQL语句信息
logImpl = STDOUT_LOGGING (Mybatis自带标准日志功能)
logImpl = log4j (自定义日志功能,需要编写log4j.properties配置文件,灵活多变,功能更加强大。)
Mybatis官网日志配置 https://mybatis.org/mybatis-3/zh/logging.html
-开启驼峰命名自动映射
设置为true,可以将数据库经典下划线命名字段a_name对应实体类以驼峰命名的属性aName。
-->
<settings>
<!-- <setting name="logImpl" value="log4j"/>-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--【别名】使用缩写词来代替冗余的类全限定名,仅用于XML文件中。
-typeAlias元素给个别类添加别名
-package元素给指定包下的类添加别名,默认为首字母小写的类名。也可在类的上方添加@Alias注解自定别名。
-->
<typeAliases>
<!-- <typeAlias alias="emp" type="com.example.pojo.Employees"/>-->
<package name="com.example.pojo"/>
</typeAliases>
<!--【环境】
-environments元素default属性指定当前使用的环境
-environment元素可以有多个。
默认使用JDBC的事务管理器。
配置数据库连接池,引用外部配置文件的连接字段。
-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</dataSource>
</environment>
</environments>
<!--【Mapper映射器注册】
方式一(推荐):使用resource属性绑定xml文件,路径使用././格式。(缺点:没有智能提示)
方式二:使用class属性绑定接口文件。(要求在接口的同级目录下有同名的*Mapper.xml文件)
方式三:使用package元素将指定包下的所有映射器接口全部注册为映射器。(要求与方式二相同)
-->
<mappers>
<mapper resource="mybatis\mapper\EmpMapper.xml"/>
<!-- <mapper resource="mybatis/mapper/UserMapper.xml"/>-->
<!-- <mapper class="com.example.dao.UserMapper"/>-->
<!-- <package name="com.example.dao"/>-->
</mappers>
</configuration>
2.加载核心配置文件,获取SqlSessionFactory实例。
SqlSessionFactory主要用于获取sqlSession的实例,sqlSession提供了数据库执行SQL命令所需的所有方法。
//SqlSessionFactory --> sqlSession
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//通过SqlSessionFactory获取sqlSession的实例
//SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
3.应用
第一步,编写接口类。
public interface UserMapper {
List<User> getUserList();
}
第二步,编写对应的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接口-->
<mapper namespace="com.kuang.mapper.UserMapper">
<select id="getUserList" resultType="com.kuang.pojo.User">
select * from mybatis.user
</select>
</mapper>
第三步,在核心配置文件中注册映射器。
<mappers>
<mapper resource="com/kuang/mapper/UserMapper.xml"/>
</mappers>
最后一步测试
@Test
public void testMybatis(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (User user : mapper.getUserList()) {
System.out.println(user);
}
sqlSession.close();
}
以下是使用Junit获取SqlSessionFactory的测试方法
public class MybatisTest {
private static SqlSessionFactory sqlSessionFactory = null;
private SqlSession sqlSession = null;
private EmpMapper mapper;
@BeforeClass
public static void init() throws IOException {
//1.利用流读取核心配置,加载数据源
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
//2.创建SqlSessionFactory构建器对象,该对象可以用来构建一个工厂类
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.利用构建器创建SqlSessionFactory,需要引用核心文件
sqlSessionFactory = builder.build(reader);
}
@Before
public void before() throws IOException {
//4.由工厂对象打开一个SqlSession
sqlSession = sqlSessionFactory.openSession();
mapper = sqlSession.getMapper(EmpMapper.class);
}
@After
public void after(){
sqlSession.close();
}
@Test
public void t1(){
int i = mapper.insertEmp(new Emp("小龙", "市场部", new Timestamp(new Date().getTime()), BigDecimal.valueOf(1500), "普通员工"));
if(i > 0){
sqlSession.commit();
System.out.println("新增成功");
}else{
System.out.println("新增失败");
}
}
@Test
public void t2(){
int i = mapper.updateEmpById(new Emp(1, "紫怡"));
if(i > 0){
sqlSession.commit();
System.out.println("修改成功");
}else{
System.out.println("修改失败");
}
}
@Test
public void t3(){
int i = mapper.deleteEmpById(29);
if(i > 0){
sqlSession.commit();
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
}
@Test
public void t4(){
Emp emp = mapper.selectEmpById(1);
System.out.println(emp);
}
@Test
public void t5(){
List<Emp> emps = mapper.selectAll();
for (Emp emp : emps) {
System.out.println(emp);
}
}
}
三、使用注解进行简单语句的开发
我们可以使用Mybatis的注解开发一些简单映射语句,这样可以是代码显得更加简介。对于稍微复杂一点的语句Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
应用案例:
@Select("select * from \"ORDER\"")
List<Order> selectOrderList();
四、SQL语句构建器
Mybatis的SQL语句构建器能够帮助我们更加优雅的构建SQL语句
1.编写SQL语句构建器
//在此类中使用构造器,构造sql语句
public class SqlConstructor {
//定义方法,返回查询的sql语句
public String getSelectAll() {
//使用内部类的形式构建
return new SQL() {
//这个大括号 -> {} 表示构造代码块,表示每一次创建此对象,都会执行构造代码块中的代码
{
//表示查询学生表中的左右记录
SELECT("*");
FROM("user");
}
//toString()方法表示将构造的sql语句转化为一个字符串,并返回
}.toString();
}
}
2.引用SQL语句
public interface UserMapper {
@SelectProvider(type = SqlConstructor.class , method = "getSelectAll")
public abstract List<User> selectAll();
}
五、缓存
什么是缓存?
缓存就是存储在内存中的临时数据。
缓存的作用?
可以有效的帮助我们提高查询的效率,减少系统开销,提高系统的效率。
什么样的数据能使用缓存?
经常查询且不经常改变的数据。
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
Mybatis中存在两种缓存机制:一级缓存与二级缓存
1、一级缓存
一级缓存是默认开启的。它只启用了本地的会话缓存,仅仅对一个会话中的数据进行缓存。
例,下方是一次会话中,进行的两次相同的查询。
@Test
public void test2(){
//开启SqlSession会话
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
mapper.getStudentById(1);
mapper.getStudentById(1);
//关闭会话
sqlSession.close();
}
我们来查看输出日志
通过查看日志我们可以看到,从Created connection 168366创建连接到Closing JDBC Conection …只进行了一次查询的操作。这是因为两次查询是相同的,第一次查询的时候Mybatis已经将结果缓存到内存中去了,当第二次进行相同的查询的时候,是从本地的缓存中直接读取数据,就不会再去访问数据库了。
一级缓存会在什么时候失效(刷新)
1.sqlSession关闭之后
2.执行增删改操作之后
3.sqlSession.clearCache()手动清理缓存
2、二级缓存
二级缓存也叫全局缓存,它的作用域在一个namespace命名空间内(mapper映射器)中。
工作机制:
一个会话查询一条数据,这个数据先回放到当前会话的一级缓存中;
如果当前会话关闭了,这个会话对应的一级缓存就没了,但是如果我们开启了二级缓存,那么这个会话的缓存将会保存到二级缓存中去;
新的会话查询,就可以从二级缓存中获取内容;
不同的mapper查出得数据会放在自己对应的缓存(map)中;
1.在mybatis的核心配合文件中添加设置,这一步是默认开启的,但是我们还是将它显示的写出来。
<settings>
<!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。-->
<setting name="cacheEnabled" value="true"/>
</settings>
2.在需要添加二级缓存的mapper.xml中添加
<cache/>
也可以自定义参数
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
用户查询的顺序如下图示:
3.自定义缓存
我们还可以功能更加强大的缓存。
依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
Mapper配置
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<!--
maxElementsInMemory:缓存中最大允许创建的对象数
maxInMemory:设定内存中创建对象的最大值。
eternal:设置元素(译注:内存中对象)是否永久驻留。如果是,将忽略超时限制且元素永不消亡。
timeToIdleSeconds:设置某个元素消亡前的停顿时间。
timeToLiveSeconds:为元素设置消亡前的生存时间.
overflowToDisk:设置当内存中缓存达到 maxInMemory 限制时元素是否可写到磁盘上。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
diskPersistent:重启时内存不持久化到硬盘。
-->
<diskStore path="java.io.tmpdir"/>
<defaultCache maxElementsInMemory="10000" memoryStoreEvictionPolicy="LRU" eternal="false"
timeToIdleSeconds="300" timeToLiveSeconds="300" overflowToDisk="false" diskPersistent="false" />
<cache name="districtDataCache"
maxElementsInMemory="4000"
eternal="true"
overflowToDisk="false"
diskPersistent="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
查看官方文档 Mybatis缓存
六、动态SQL
Mybatisj的动态SQL功能在于可以使用它的动态SQL元素根据不同条件拼接 SQL 语句。
Mybatis动态SQL官方文档
1.满足条件就加入(IF元素)
<if test="title != null">
and title like = #{title}
</if>
2.多个条件中选其一(choose when otherwise元素)
choose要求与when一起使用。
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
3、动态插入where语句(where元素)
where元素下存在内容(语句)的才会插入where子句,且会删除子句开头的and或or。
<where>
<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>
</where>
4.用于in条件语句与批量操作的元素(Foreach元素)
collection属性值为遍历的集合名称,传入的参数如果没有使用@Param定义的话。默认的名称都是list。
item为迭代的元素。
index为迭代的元素下标。
open定义在语句开始前加入的字符。
close定义在语句结束后加入的字符。
separator定义插入迭代参数item之间的分隔符。
foreach可以迭代的元素有List、Set、Map、数组。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
6.用于在语句前后添加/删除前缀后缀(trim)
prefix 给语句添加前缀
prefixOverrides 删除语句的前缀
suffix 给语句添加后缀
suffixOverrides 删除语句的后缀
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
7.用于动态插入(set元素)
set元素会去除语句后面多余的逗号。
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
8.用于复用的SQL语句(SQL片段)
<select id="getTeacherByIF" resultType="teacher" parameterType="map">
select * from teacher
where 1 = 1
<include refid="if-id-name"></include>
</select>
<sql id="if-id-name">
<if test="id != null">
and id = #{id}
</if>
<if test="name != null">
and name = #{name}
</if>
</sql>
七、结果集映射及一对多多对一的关系处理案例
MyBatis 创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序,那就太好了,但可惜也没有。 MyBatis的ResultMap作用就是为了解决数据库库字段名与实体类属性名不同的问题
。
因为官方的解释有些地方比较生涩难懂,这里就我的个人实践在结合实例对Mybatis的高级结果集映射做一个更加简单易懂的讲解。附官方文档:Mybatis结果集映射
一、(案例)处理老师与学生一对多的结果集映射
注意这里不提供测试,只做讲解。请自行进行测试。
首先分析老师与学生的关系是一个老师对应多个学生,即一个老师可能教多个学生。在数据库中两者关系是使用外键关联的,我们先来建立两个表的实体类,如下:
//学生类
public class Student {
private Integer id;
private String name;
}
//老师类
public class Teacher {
private Integer id;
private String name;
}
实体类建好了,现在如果我们需要查询一个老师的信息及这个老师教的学生们。这时候就发现问题了,这里的查询需要进行连表,且查询的结果集我们如何进行封装,这时候我们就会疑惑了。怎么做呢?我们可以在老师类中添加一个学生类的集合。
public class Teacher {
private Integer id;
private String name;
//一个老师对应多个学生
private List<Student> students;
}
那么如何将结果集映射到这个实体类中,就可以使用Mybatis的结果集映射。省略接口与方法的定义,这里只讲映射器的实现。使用结果集映射的子查询方式查询老师的详细信息,代码如下:
<!--
属性:
reslutMap 用于指定映射器
-->
<select id="getTeacherById" resultMap="TeacherMap">
select * from teacher where id = #{id}
</select>
<!--
标签:
resultMap 定义一个映射器
result 映射普通结果集
collection 映射一个集合
属性:
id 指定映射器名称
type 映射器映射的类型
property 要映射的属性
column 数据库字段
select 指定一个select映射
ofType 指定集合的泛型(即集合装的对象)
javaType 指定对象的Java类型
-->
<resultMap id="TeacherMap" type="teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" select="getStudentById" column="id" ofType="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
</collection>
</resultMap>
<!--根据老师id查询学生-->
<select id="getStudentById" resultType="student">
select * from student where tid = #{id}
</select>
这里使用了子查询的实现方式,也可以直接使用连表查询直接映射到结果集中的方式。
二、(案例)处理多对多关系的结果集映射
实际生活的老师与学生的关系是怎么样的,看如下对象关系的分析图。
老师 | 学生 |
---|---|
1 | N |
N | 1 |
上图中将老师一列的数值相乘结果为N,同学一列的数值相乘结果同样是N。他们的关系即是多对多的关系。这种方法有个口诀“上下相乘”。
1.建立学生与老师的实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private Integer id;
private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer id;
private String name;
private Integer tid;
//一个学生对应一个老师
private Teacher teacher;
}
2.定义StudentMapper接口
public interface StudentMapper {
public Student getStudent();
public Teacher getTeacher(int id);
}
编写xml实现,这里提供了两种实现方式,子查询与连表的方式。一定不要忘记注册Mapper。
<?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接口-->
<mapper namespace="com.kuang.mapper.StudentMapper">
<!--方式一:子查询(嵌套查询,SQL语句较简单,结果集映射较为复杂)-->
<!-- <select id="getStudent" resultMap="StudentTeacher">-->
<!-- select * from student-->
<!-- </select>-->
<!-- <select id="getTeacher" resultType="teacher">-->
<!-- select * from teacher where id = #{id}-->
<!-- </select>-->
<!-- <resultMap id="StudentTeacher" type="student">-->
<!-- <result property="id" column="id"/>-->
<!-- <result property="name" column="name"/>-->
<!-- <!–复杂的属性,我们需要单独处理对象; association 集合; collection–>-->
<!-- <association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>-->
<!-- </resultMap>-->
<!--方式二:连表查询(SQL语句麻烦,编写结果集映射更加简单)-->
<select id="getStudent" resultMap="StudentTeacher">
select s.id sid, s.name sname,t.id tid ,t.name tname from student s,teacher t where s.tid = t.id
</select>
<resultMap id="StudentTeacher" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="name" column="tname"/>
<result property="id" column="tid"/>
</association>
</resultMap>
</mapper>
解决使用xml文件编写SQL语句时特殊字符插入的问题
1、xml文档关键字插入
Mybatis将SQL语句与程序分离,改为使用xml文件来编写我们的SQL语句。但有时我们在编写语句时由于xml的语法格式问题导致有些特殊字符无法使用。如<>号这个在xml文档中被认为是元素的开闭标签。这时候我们可以使用转义字符来代替这些特殊字符。
2、SQL语句通配符插入
在进行模糊查询的数据库操作中一般会存在一些像%、_、[]这类的通配符,这些通配符会被数据库所转义。如果我们不希望将这些通配符被转义,而是被数据库看为是普通字符,可以使用Escape关键字。
# '&M%'中M后面的%将被看做一个普通字符%
LIKE '%M%' ESCAPE 'M'
Escape关键字的作用是将指定字符后的通配符看为是一个普通字符。
Mysql数据库SQl语句应用
1、mysql中进行表字段的模糊查询。
<select id="selectEmpList" resultType="emp" parameterType="map">
select * from emp
<where>
isusable = 0
<if test="empname!=null">
and name like concat('%',#{empname},'%') escape '/'
</if>
<if test="empphone!=null">
and phone like concat('%',#{empphone},'%') escape '/'
</if>
</where>
</select>
Oracle数据库SQL语句应用
1、在Oracle数据库中进行表数据新增。
已知Oracle中的主键字段需要使用序列来生成,那么新增一条语句之间就必须先使用selectKey标签获得主键,将其封装到实体类的主键字段中,在进行新增操作。
正确的新增语句如下:
<insert id="insertCommodity">
<selectKey resultType="int" keyProperty="cid" order="BEFORE">
select commodity_sq.nextval from dual
</selectKey>
insert into commodity(cid,cname,integral,state,price) values (#{cid},#{cname},#{integral},#{state},#{price})
</insert>
2、在Oracle数据库中进行模糊查询。
在Oracle查询语句中concat一次只能连接两个字符串。
<select id="selectCommodityByNameOrIntegral" resultType="com.example.pojo.Commodity">
select * from commodity
<where>
<if test="cname != null">
CNAME like concat(CONCAT('%',#{cname}),'%')
</if>
</where>
</select>
3、Oracle数据库进行批量插入
<insert id="insertOrderDetail">
insert into ORDER(ORDER_ID,MONEY)
select order_sq.nextval,A.* from (
<foreach collection="list" item="od" separator="union all" >
select #{od.money} from dual
</foreach>
) A
</insert>
SQL语句亲测
select ORDER_SQ.nextval,A.* from (
select 1 from dual union all
select 2 from dual union all
select 3 from dual
) A;
结果会生成一张3行2列的临时表,且序列会自增。