简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
入门
环境搭建
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
写properties文件
将连接数据库要用到的信息写入一个properties文件中:
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8
username = root
password = 666666
配置mybatis-XML
XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。这里先给出一个简单的示例:
<?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>
<properties resource="db.properties"/> <!--引入properties文件-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/> <!--事务管理器-->
<dataSource type="POOLED"> <!--数据源,有连接池-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
</configuration>
构建 SqlSessionFactory
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory
的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder
获得。从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。MyBatis 包含一个名叫 Resources 的工具类,使得从类路径或其它位置加载资源文件更加容易。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
获取 SqlSession
既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。为了方便,将其封装在一个工具类中:
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory = null;
static {
String resource = "mybatis-config.xml";
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
如果不支持带资源的try块,是因为maven项目默认使用的JDK版本过低,在pom.xml文件中加上配置:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>12</source>
<target>12</target> <!--1.7之后都支持-->
</configuration>
</plugin>
</plugins>
</build>
创建POJO实体类
创建了一个简单的User表用作测试,后续我们要执行获取数据库中用户的操作:
随后写出对应的实体类:
package com.pickinstars.POJO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private long id;
private String name;
private String password;
}
编写UserMapper接口
目前只含有一个得到所有用户的方法:
public interface UserMapper {
ArrayList<User> getUsers();
}
编写UserMapper.XML文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pickinstars.Mapper.UserMapper">
<select id="getUsers" resultType="com.pickinstars.POJO.User">
select * from mybatis.user;
</select>
</mapper>
参数解释:
- namespace:对应mapper接口
- id:对应接口中的某个方法
- resultType:返回的结果类型,这里是User型
注册Mapper
在mybatis的XML配置文件中注册刚刚写好的XML
<mappers>
<mapper resource="com/pickinstars/Mapper/UserMapper.xml"/>
</mappers>
测试
public class UserMapperTest {
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
ArrayList<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
}
成功输出了数据库中所有User的信息。
注意:如果xml文件没有放在maven的resources目录下的话(如下图):
需要在pom.xml中添加配置:
<build>
<resources>
<resource>
<!--所在的路径-->
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
作用域和生命周期
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,在应用运行期间不要重复创建多次。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。
增删查改
在上述过程中,已经将框架搭建好了,之后再写数据库的业务代码,只需要在上述定义的UserMapper.xml
中进行sql的编写就可以了。
上方的test中实现了简单的查询,下面来看带参数的查询:
1.编写接口
User getUserById(long id);
2.在xml中编写sql
<select id="getUserById" parameterType="long" resultType="com.pickinstars.POJO.User">
select * from mybatis.user where id = #{id};
</select>
parameterType是参数类型,要与接口一致,其中 #{id} 可以直接取到接口定义要传入的id。
3.最后就是测试了
同样的,如果要插入一条信息到数据库:
void insertUser(User user);
<!-- {}中的内容要和实体类中字段的名字一致 -->
<insert id="insertUser" parameterType="com.pickinstars.POJO.User">
insert into mybatis.user (id, name, pwd) value (#{id},#{name},#{password});
</insert>
测试:有关数据库修改的都需要commit
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User(4,"梵","500191");
mapper.insertUser(user);
sqlSession.commit(); // ✳
System.out.println("ok");
sqlSession.close();
}
关于update
,delete
不再赘述。
别名
在上述例子中,可以看到resultType="com.pickinstars.POJO.User"
非常冗长,我们可以用别名来进行简化,只需要在xml中加入:
<typeAliases>
<typeAlias type="com.pickinstars.POJO.User" alias="TestUser"/>
</typeAliases>
我们就可以用直接使用TestUser
来替换
还有一种方式是指定一个包,这个包下所有实体类默认类名作为别名:
<typeAliases>
<package name="com.pickinstars.POJO"/>
</typeAliases>
在指定包的情况下如果想自定义别名,可以在给类加一个注解,例如:
@Alias("TestUser")
注解开发
对于结构比较简单的SQL语句,可以使用注解开发,不需要写在XML文件中,只需要在接口中提供相应SQL的注解,再去mapper注册器中注册这个接口类就可以使用了,例如:
@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);
<mappers>
<mapper class="com.pickinstars.Mapper.UserMapper"/>
</mappers>
对于较复杂的语句,建议使用xml的方式。
映射
当POJO中的属性名与数据库中的字段名不一致时,就无法注入合理的值,例如,在我的User实体类中有一个password属性,对应数据库中的pwd字段,如果直接将ResultType
设置为这个类,取得的user的password属性为null。虽然可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。还可以有更优雅的方式:使用ResultMap
。
<!--与下方对应-->
<resultMap id="userResultMap" type="TestUser">
<id property="password" column="pwd"/>
</resultMap>
<!--这里使用resultMap-->
<select id="getUserById" parameterType="long" resultMap="userResultMap">
select * from mybatis.user where id = #{id};
</select>
关联映射
对于一个有关联的数据库,例如学生和老师,多个学生对应一个老师的这种关系,创建两张表模拟:
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO teacher(`id`, `name`) VALUES (1, '星宿');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
学生的实体类如下:
package com.pickinstars.POJO;
import lombok.Data;
@Data
public class Student {
private int id;
private String name;
private Teacher teacher;
}
可以发现,在数据库中不存在一个teacher的字段对应这个实体类中的teacher属性。那么假如我们要查询所有学生的信息,该怎么做呢?
首先还是先编写接口:
package com.pickinstars.Mapper;
import com.pickinstars.POJO.Student;
import java.util.ArrayList;
public interface StudentMapper {
ArrayList<Student> getStudentList();
}
接下来再编写XML绑定时就有两种实现方式:查询嵌套和结果嵌套
查询嵌套处理
类似于SQL语句中的子查询(嵌套查询)。使用resultMap
中的association
属性表实多对一的关联关系。
association
中嵌套了一个select字句,相当于分成两次查询。
<select id="getStudentList" resultMap="StudentList">
select * from student;
</select>
<resultMap id="StudentList" type="Student">
<!--在Student类型的结果中有一个teacher的属性,对应字段为tid,Java类型为Teacher,通过select得到这个Teacher-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--子查询-->
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{tid};
</select>
结果嵌套处理
由于我们查询出的结果中有teacher属性,这是一个Teacher类,而Teacher类中有id和name属性,所以我们把需要的信息都用联表查询查出来,在结果映射中,单独对无法直接处理的teacher做映射,解释这个属性的javaType
以及这个类型属性对应的字段即可:
<resultMap id="StudentList" type="Student">
<association property="teacher" javaType="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
<select id="getStudentList" resultMap="StudentList">
select s.id sid,s.name sname,s.tid tid,t.name tname
from mybatis.student s ,mybatis.teacher t
where tid = t.id;
</select>
集合映射
现在来讨论多对一的映射,老师对应的实体类如下:
package com.pickinstars.POJO;
import lombok.Data;
import java.util.ArrayList;
@Data
public class Teacher {
private int id;
private String name;
private ArrayList<Student> students;
}
一个老师对应多个学生,包含了一个ArrayList
类型的属性
还是从两种方法来从数据库中根据老师的id查出结果
查询嵌套处理
这里改用collection
标签和里面的ofType
属性,原理类似:
<select id="getTeacherById" resultMap="getTeacher">
select *
from mybatis.teacher t
where id = #{id};
</select>
<resultMap id="getTeacher" type="Teacher">
<result property="id" column="id"/>
<collection property="students" ofType="Student" column="id" select="getStudents"/>
</resultMap>
<select id="getStudents" resultType="Student">
select *
from mybatis.student s
where s.tid = #{id};
</select>
结果嵌套处理
<select id="getTeacherById" resultMap="getTeacher">
select t.id tid,t.name tname,s.id sid,s.name sname
from mybatis.teacher t ,mybatis.student s
where s.tid = t.id and t.id = #{id};
</select>
<resultMap id="getTeacher" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result column="sid" property="id"/>
<result column="sname" property="name"/>
</collection>
</resultMap>
动态SQL
动态SQL就是根据不同的条件生成不同的SQL语句。参考
sql片段
对于经常使用到的一些sql代码片段,为了提高复用性,可以将其写在<sql>
标签中,在需要使用的地方,用include
标签引入即可,id要对应,例如:
<sql id="mytest">
select t.id tid,t.name tname,s.id sid,s.name sname
from mybatis.teacher t ,mybatis.student s
where s.tid = t.id and t.id = #{id};
</sql>
<select id="getTeacherById" resultMap="getTeacher">
<include refid="mytest"/>
</select>
缓存
Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率。
Mybatis系统中中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,一级缓存开启(sqlSession级别的缓存,也称s本地缓存)
- 二级缓存需要手动开启和配置,是基于namespace级别的缓存
- 为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存
一级缓存
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 之后再获取相同的数据,会直接从缓存中获取,不会再执行SQL查询数据库
缓存失效的情况:
- 查询不同的东西
- 增删改操作,可能会改变原来的数据,所以会刷新缓存
- 查询不同的Mapper.xml
- 手动清理缓存
sqlSession.clearCache()
二级缓存
工作机制:
- 一个会话查询一条数据,这个会话就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话的一级缓存就没了;但是我们希望会话关闭后,一级缓存中的数据会被保存在二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查询出的数据会放在自己对应的缓存中
在mapper.xml中使用<cache/>
就可以使用二级缓存,例如:
<cache eviction="FIFO" <!--先进先出-->
flushInterval="60000"
size="520"
readOnly="true"/>