前言
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
在这里,通过写个几例子来说明。
配置文件
核心配置文件:SqlMapConfig.xml
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--导入外部配置文件-->
<properties resource="db.properties"/>
<!--延迟加载-->
<settings>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--关闭积极加载-->
<setting name="aggressiveLazyLoading" value="false"/>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
<!--别名配置-->
<typeAliases>
<package name="com.abc.entity"/>
</typeAliases>
<!--配置类型处理器-->
<typeHandlers>
<!--枚举处理器-->
<!--注意:mybatis提供了两种不同的枚举处理器,默认情况下使用的是EnumTypeHandler(字符串)-->
<!--此处我们修改默认枚举处理器,使用EnumOrdinalTypeHandler来操作,使用的是枚举下标-->
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.abc.entity.Gender"/>
</typeHandlers>
<!--mybatis插件配置-->
<plugins>
<!--pagehelper分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库-->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 数据库连接池-->
<dataSource type="POOLED">
<!--使用p6spy探测SQL语句,替换原有驱动-->
<!--<property name="driver" value="com.mysql.cj.jdbc.Driver"/>-->
<property name="driver" value="${driver}"/>
<!--并且修改URL-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--扫描实体包中所有的接口和xml映射文件-->
<mappers>
<package name="com.abc.mapper"/>
</mappers>
</configuration>
其它用到的或要用的配置文件:
db.properties
driver=com.p6spy.engine.spy.P6SpyDriver
url=jdbc:p6spy:mysql:///study?serverTimezone=GMT%2B8
username=root
password=123456
ehcache.xml
<ehcache>
<!--二级缓存存储路径-->
<diskStore path="D:\java\ehcache"/>
<!--
maxElementsInMemory 内存中存储缓存对象的最大值
maxElementsOnDisk 磁盘中存储缓存对象的最大值
eternal 缓存对象是否是永久的
overflowToDisk 缓存对象是否允许被写到磁盘中
timeToIdleSeconds 缓存对象最大空闲时间,超时将被销毁
timeToLiveSeconds 缓存对象最长存活时间,超时将被销毁
-->
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="600"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
<!--可以配置多套缓存配置-->
<!-- <cache name="myCache1"
maxElementsInMemory="1000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="600">
</cache>-->
</ehcache>
log4j.properties
# 全局日志配置
log4j.rootLogger=WARN, stdout
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
# MyBatis 日志配置
log4j.logger.com.gem.mapper=DEBUG
spy.properties
driverlist=com.mysql.cj.jdbc.Driver
logMessageFormat=com.p6spy.engine.spy.appender.SingleLineFormat
databaseDialectDateFormat=yyyy-MM-dd HH:mm:ss
appender=com.p6spy.engine.spy.appender.StdoutLogger
用到的依赖 jar包
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
<dependency>
<scope>compile</scope>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<scope>compile</scope>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<!--通用mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.1.5</version>
<scope>compile</scope>
</dependency>
</dependencies>
单表操作
mybatis_student 学生表
对应的实体类:
student 学生类
@Data
public class Student implements Serializable {
private static final long serialVersionUID = 7557762079548382677L;
private Long id;
private String name;
private LocalDate birthday;
private Gender gender;
}
Gender 性别枚举类
public enum Gender {
MALE,
FEMALE;
}
持久层Mapper接口及XML实现
StudentMapper 接口
public interface StudentMapper {
int insertStudent(Student student);
int deleteStudent(Long id);
int updateStudent(Student student);
List<Student> selectStudentsByIds(@Param("ids") List<Long> ids);
List<Student> selectStudentsByBirthday(
@Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
List<Student> selectStudentsByConditions(
@Param("name") String name, @Param("gender") Gender gender);
Student selectStudentById(Long id);
List<Student> selectStudents();
List<Student> selectStudentsByName(String name);
}
StudentMapper.xml 映射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命名空间,要求值与对应的接口保持一致-->
<mapper namespace="com.abc.mapper.StudentMapper">
<!--指定当前mapper开启二级缓存-->
<!-- <cache/>-->
<!--
默认内置二级缓存参数配置:
eviction 表示缓存策略,LRU,FIFO
flushInterval 表示刷新间隔(毫秒)
size 表示缓存容量(默认1024)
readOnly 表示缓存只读
-->
<!-- <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>-->
<!--使用ehcache缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<!--<property name="" value=""/>-->
</cache>
<!--
查询
id 对应接口中的方法名
resultType 对应查询结果类型
a.查询结果为集合时,它指向的是集合元素类型
b.默认情况下需要指定类的全限定名
可以在SqlMapConfig.xml中配置别名定义来简化
parameterType 指向参数类型
-->
<select id="selectStudents" resultType="student">
select * from mybatis_student
</select>
<!--parameterType参数类型可以省略-->
<select id="selectStudentById" parameterType="long" resultType="student">
select * from mybatis_student where id = #{id}
</select>
<select id="selectStudentsByBirthday" parameterType="java.time.LocalDate" resultType="student">
select * from mybatis_student where birthday between #{startDate} and #{endDate}
</select>
<!--
注意:
通过$的方式拼接的sql语句,不再是以占位符的形式生成sql,而是以拼接字符串的方式生成sql,
这样做带来的问题是:会引发sql注入的问题!
-->
<select id="selectStudentsByName" resultType="student">
select * from mybatis_student where name like concat('%',#{name},'%')
</select>
<!--添加操作-->
<insert id="insertStudent">
insert into mybatis_student(name, birthday, gender)
values (#{name}, #{birthday}, #{gender})
</insert>
<!--删除-->
<delete id="deleteStudent">
delete from mybatis_student where id = #{id}
</delete>
<!--更新-->
<!--动态SQL语句-->
<update id="updateStudent">
update mybatis_student set id=#{id}
<if test="name != null and name != ''">
,name=#{name}
</if>
<if test="birthday !=null">
,birthday=#{birthday}
</if>
<if test="gender !=null">
,gender=#{gender}
</if>
<where>
id=#{id}
</where>
</update>
<!--定义SQL片段-->
<sql id="query_student_condition">
<where>
<if test="name!=null and name!=''">
and name=concat('%',#{name},'%')
</if>
<if test="gender!=null">
and gender=#{gender}
</if>
</where>
</sql>
<!--where标签可以提供where关键字,并且把条件第一个and自动去除-->
<select id="selectStudentsByConditions" resultType="student">
select * from mybatis_student
<!--引用SQL片段-->
<!--注意:如果想要引用其他xml文件中的SQL片段,必须指定全名(命名空间+sqlid)-->
<include refid="query_student_condition"></include>
</select>
<!--
动态SQL之循环语句foreach
collection 表示迭代的集合或者数组
open 表示迭代开始的内容
close 表示迭代结束的内容
item 表示迭代的每一个项
separator 表示迭代间隔符
-->
<select id="selectStudentsByIds" resultType="student">
select * from mybatis_student
<where>
<if test="ids!=null and ids.size>0">
<foreach collection="ids" open="id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
</mapper>
把测试的基本步骤封装成 MybatisUtil工具类
/** Mybatis工具类 */
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory = null;
// 本地线程变量,可以保证线程安全
private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<>();
static {
try {
// 加载mybatis核心配置文件,获取SqlSessionFactory连接工厂
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();
}
}
/** 返回SqlSessionFactory对象 */
public static SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
/** 新建一个SqlSession对象,并且把它放入到线程变量中 */
private static void newSqlSession() {
// 通过连接工厂获取SqlSession连接对象
// 注意:openSession参数为true表示该连接对象开启自动提交模式
SqlSession sqlSession = sqlSessionFactory.openSession(true);
threadLocal.set(sqlSession);
}
/** 获取SqlSession对象 */
public static SqlSession getSqlSession() {
// 优先从线程变量中获取SqlSession对象
SqlSession sqlSession = threadLocal.get();
// 判断,如果线程变量中没有SqlSession对象,则创建并且添加
if (sqlSession == null) {
newSqlSession();
sqlSession = threadLocal.get();
}
return sqlSession;
}
/** 关闭SqlSession对象,并且从线程变量中删除 */
public static void closeSqlSession() {
// 获取线程变量中的SqlSession对象
SqlSession sqlSession = threadLocal.get();
// 判断
if (sqlSession != null) {
sqlSession.close();
threadLocal.set(null);
}
}
}
StudentMapperTest 测试类
public class StudentMapperTest {
SqlSession sqlSession;
StudentMapper mapper;
// 任何测试方法运行之前会优先执行该方法
@Before
public void setUp() {
sqlSession = MybatisUtil.getSqlSession();
// 获取Mapper接口的动态代理对象
mapper = sqlSession.getMapper(StudentMapper.class);
}
// 任何测试方法运行之后会继续执行该方法
@After
public void tearDown() {
MybatisUtil.closeSqlSession();
}
@Test
public void insertStudent() {
Student student = new Student();
student.setName("jack");
student.setBirthday(LocalDate.now());
student.setGender(Gender.FEMALE);
int result = mapper.insertStudent(student);
System.out.println("添加成功,一共影响了: " + result + "行记录.");
}
@Test
public void deleteStudent() {
int result = mapper.deleteStudent(4L);
System.out.println("删除成功,一共影响了: " + result + "行记录.");
}
@Test
public void updateStudent() {
Student student = mapper.selectStudentById(1L);
student.setName("张三");
int result = mapper.updateStudent(student);
System.out.println("更新成功,一共影响了: " + result + "行记录.");
}
@Test
public void selectStudentsByBirthday() {
mapper
.selectStudentsByBirthday(LocalDate.of(2020, 9, 10), LocalDate.of(2020, 9, 20))
.forEach(System.out::println);
}
@Test
public void selectStudentById() {
System.out.println(mapper.selectStudentById(1L));
}
@Test
public void selectStudents() {
mapper.selectStudents().forEach(System.out::println);
}
@Test
public void selectStudentsByName() {
mapper.selectStudentsByName("o").forEach(System.out::println);
}
@Test
public void selectStudentsByConditions() {
mapper.selectStudentsByConditions(null, Gender.MALE).forEach(System.out::println);
}
@Test
public void selectStudentsByIds() {
mapper.selectStudentsByIds(List.of(1L, 3L, 2L)).forEach(System.out::println);
}
}
多表级联操作(一对一)
一对一关系:丈夫和妻子
mybatis_husband 丈夫表
mybatis_wife 妻子表
对应的实体类:
Husband 类
@Data
public class Husband {
private Long id;
private String name;
// 体现一对一关系
private Wife wife;
}
Wife 类
@Data
public class Wife {
private Long id;
private String name;
}
持久层Mapper接口及XML实现
HusbandMapper 接口
public interface HusbandMapper {
/** 根据姓名查询丈夫信息,如果该丈夫有妻子,则积极加载妻子的信息 */
Husband selectHusbandAndWife(String name);
/** 根据姓名查询丈夫信息,如果该丈夫有妻子,则延迟加载妻子的信息 */
Husband selectHusbandAndWifeLazy(String name);
Wife selectWifeById(Long id);
}
HusbandMapper.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.abc.mapper.HusbandMapper">
<!--定义resultmap-->
<resultMap id="husbandAndWifeMap" type="Husband">
<!--id封装-->
<id property="id" column="id"/>
<!--其他属性封装-->
<result property="name" column="name"/>
<!--关联绑定(一对一)-->
<association property="wife" javaType="Wife">
<!--绑定关联对象的属性-->
<id property="id" column="wid"/>
<result property="name" column="wname"/>
</association>
</resultMap>
<select id="selectHusbandAndWife" resultMap="husbandAndWifeMap">
select h.id, h.name, w.id wid, w.name wname
from mybatis_husband h
left join mybatis_wife w on h.id = w.husband_id
where h.name = #{name}
</select>
<!--延迟加载-->
<resultMap id="husbandAndWifeLazyMap" type="Husband">
<id property="id" column="id"/>
<result property="name" column="name"/>
<association property="wife" javaType="Wife" select="selectWifeById" column="id"/>
</resultMap>
<select id="selectHusbandAndWifeLazy" resultMap="husbandAndWifeLazyMap">
select *
from mybatis_husband
where name = #{name}
</select>
<select id="selectWifeById" resultType="Wife">
select *
from mybatis_wife
where husband_id = #{id}
</select>
</mapper>
这里就不写测试类了。
多表级联操作(一对多)
一对多关系:顾客和订单
mybatis_customer 顾客表
mybatis_order 订单表
对应的实体类:
Customer 顾客类
@Data
public class Customer {
private Long id;
private String name;
// 体现一对多关系
private List<Order> orders;
}
Order 订单类
@Data
public class Order {
private Long id;
private String orderno;
private Double price;
}
持久层Mapper接口及XML实现
CustomerMapper 接口
public interface CustomerMapper {
/** 根据客户名查询客户,如果该客户有订单,则级联查询该客户的订单信息 */
Customer selectCustomerAndOrders(String name);
/** 添加客户,如果该客户有订单,则级联添加订单信息 */
int insertCustomerAndOrders(Customer customer);
}
CustomerMapper.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.abc.mapper.CustomerMapper">
<resultMap id="customerAndOrdersMap" type="Customer">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!--一对多关系绑定-->
<collection property="orders" ofType="Order">
<id property="id" column="oid"/>
<result property="orderno" column="orderno"/>
<result property="price" column="price"/>
</collection>
</resultMap>
<select id="selectCustomerAndOrders" resultMap="customerAndOrdersMap">
select c.id, c.name, o.id oid, o.orderno, o.price
from mybatis_customer c
left join hbm_order o on c.id = o.customer_id
where c.name = #{name}
</select>
<!--
useGeneratedKeys用来把添加自动生成的id值赋值给keyProperty
-->
<insert id="insertCustomerAndOrders" useGeneratedKeys="true" keyProperty="id">
insert into mybatis_customer (name)
values (#{name})
</insert>
</mapper>
CustomerMapperTest 测试类
public class CustomerMapperTest {
SqlSession sqlSession;
CustomerMapper mapper;
@Before
public void setUp() {
sqlSession = MybatisUtil.getSqlSession();
mapper = sqlSession.getMapper(CustomerMapper.class);
}
@After
public void tearDown() {
MybatisUtil.closeSqlSession();
}
@Test
public void selectCustomerAndOrders() {
System.out.println(mapper.selectCustomerAndOrders("张三"));
}
@Test
public void insertCustomerAndOrders() {
Customer customer = new Customer();
customer.setName("jack");
mapper.insertCustomerAndOrders(customer);
}
}
补充:缓存测试
TestMybatisCache 测试类
public class TestMybatisCache {
/** 测试Mybatis一级缓存
* 一级缓存默认开启,无需配置
* 它的作用域为单独的某一个SqlSession
*/
@Test
public void testFirstLevelCache() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student s1 = mapper.selectStudentById(1L);
System.out.println(s1);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
Student s2 = mapper.selectStudentById(1L);
System.out.println(s2);
System.out.println(s1 == s2);
MybatisUtil.closeSqlSession();
}
/**
* 测试Mybatis二级缓存
* 二级缓存默认关闭,需要手动配置打开
* 它的作用域是跨多个SqlSession的,即多个连接可以共享
* 打开方式:
* 1.全局配置文件开启总开关
* 2.开启二级缓存局部小开关(哪个操作是需要缓存的)
* 3.需要二级缓存的实体实现序列化接口
*
* 4.如果使用第三方缓存框架,则需要额外配置:
* 4-1.导入第三方缓存jar包(此处我们使用ehcache),mybatis-ehcache
* 4-2.导入缓存配置文件
* 4-3.在映射文件中指定二级缓存的type类型
*/
@Test
public void testSecondLevelCache() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student s1 = mapper.selectStudentById(1L);
System.out.println(s1);
MybatisUtil.closeSqlSession();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
sqlSession = MybatisUtil.getSqlSession();
mapper = sqlSession.getMapper(StudentMapper.class);
Student s2 = mapper.selectStudentById(1L);
System.out.println(s2);
System.out.println(s1 == s2);
MybatisUtil.closeSqlSession();
}
}
一级缓存测试结果:
DEBUG [main] - Cache Hit Ratio [com.abc.mapper.StudentMapper]: 0.0
DEBUG [main] - ==> Preparing: select * from mybatis_student where id = ?
DEBUG [main] - ==> Parameters: 1(Long)
1600950867935|128|statement|connection 0|url jdbc:p6spy:mysql:///study?serverTimezone=GMT%2B8|select * from mybatis_student where id = ?|select * from mybatis_student where id = 1
DEBUG [main] - <== Total: 1
Student(id=1, name=张三, birthday=2020-09-16, gender=MALE)
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DEBUG [main] - Cache Hit Ratio [com.abc.mapper.StudentMapper]: 0.0
Student(id=1, name=张三, birthday=2020-09-16, gender=MALE)
true
二级缓存测试结果:
DEBUG [main] - Cache Hit Ratio [com.abc.mapper.StudentMapper]: 0.0
DEBUG [main] - ==> Preparing: select * from mybatis_student where id = ?
DEBUG [main] - ==> Parameters: 1(Long)
1600951052911|6|statement|connection 0|url jdbc:p6spy:mysql:///study?serverTimezone=GMT%2B8|select * from mybatis_student where id = ?|select * from mybatis_student where id = 1
DEBUG [main] - <== Total: 1
Student(id=1, name=张三, birthday=2020-09-16, gender=MALE)
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DEBUG [main] - Cache Hit Ratio [com.abc.mapper.StudentMapper]: 0.5
Student(id=1, name=张三, birthday=2020-09-16, gender=MALE)
true