上一篇文章我们介绍了SpringBoot集成JdbcTemplate.简单体验了一下JdbcTemplate框架的用法,今天的内容比较重要,我们来介绍一下SpringBoot集成Mybatis的步骤。
1、 Mybatis 介绍
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)。
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手工设置参数以及抽取结果集。MyBatis 使用简单的 XML 或注解来配置和映射基本体,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
Mybatis特点:
1、Mybatis实现了接口绑定,使用更加方便。
2、对象关系映射的改进,效率更高
3、MyBatis采用功能强大的基于OGNL的表达式来消除其他元素。
优点:
1、简单易学
mybatis本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件 配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
2、灵活
mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多。
3、解除sql与程序代码的耦合
通过提供DAL层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
4、提供映射标签,支持对象与数据库的orm字段关系映射
5、提供对象关系映射标签,支持对象关系组建维护
6、提供xml标签,支持编写动态sql。
缺点:
1、编写SQL语句时工作量很大,尤其是字段多、关联表多时,更是如此。
2、SQL语句依赖于数据库,导致数据库移植性差,不能更换数据库。
3、框架还是比较简陋,功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据库修改。
4、二级缓存机制不佳
上面的内容都是从网上拷贝的,因为我想大多数的人应该都用过mybatis,并且我们本文的侧重点主要是SpringBoot的集成方式,而不是从头介绍Mybatis,如果大家对Mybatis的使用不太了解,建议先去学习一下Mybatis的用法。
2、集成步骤
接下来我们开始进行集成。为了方便操作,由于我们上次刚刚集成过JdbcTemplate, 代码中共存多个DAO层框架可能会有问题,我们在原有项目基础上拉取一个新的分支来进行开发,分支名就叫 feature/mybaits。完成的代码都会托管到gitCode上,大家可在文末获取地址。
2.1 引入依赖
首先我们先引入Mybatis所需依赖,mybatis本身已经提供了用于适配springBoot的Starter, 同时我们还需要引入 mysql-connector. 在pom.xml中添加:
代码语言:javascript复制<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
<!-- mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
<!-- MySQL连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2.2 配置数据库连接
在spring的配置文件中,配置我们需要访问的数据库的连接信息,这个配置和前面jdbcTemplate的配置一样
代码语言:javascript复制spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_learning
username: root
password: root
2.3 开发Mapper
在JdbcTemplate中我们访问数据库的这一层使用dao表示的。但是在mybatis中,我们一般都把这一层称之为mapper, 并且一般类名也用这个结尾,其实代表的都是一个意思,就是使用习惯的问题。在Mybatis中的Mapper也是分为接口和实现,比较特殊的是mapper的实现一般使用xml文件的形式来体现。我们的sql也都是写在xml文件中。
我们在项目中创建一个mapper的文件夹,用来存放所有的Mapper接口。我们在里边创建UserMapper用来处理user表的增删改查操作。
代码语言:javascript复制@Mapper
public interface UserMapper {
/**
* 删除操作
* @param id
* @return
*/
int deleteByPrimaryKey(Integer id);
/**
* 插入操作
* @param record
* @return
*/
int insert(User record);
/**
* 插如操作
* @param record
* @return
*/
int insertSelective(User record);
/**
* 根据id查询操作
* @param id
* @return
*/
User selectByPrimaryKey(Integer id);
/**
* 更新操作
* @param record
* @return
*/
int updateByPrimaryKeySelective(User record);
/**
* 更新操作
* @param record
* @return
*/
int updateByPrimaryKey(User record);
}
然后在resources 资源目录下(application.yml同级目录下)创建一个mapper文件夹,用于存放xml格式的mapper实现。在里面写一个UserMapper.xml
代码语言:javascript复制<?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.lsqingfeng.springboot.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.lsqingfeng.springboot.entity.User">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="age" jdbcType="INTEGER" property="age" />
<result column="address" jdbcType="VARCHAR" property="address" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
</resultMap>
<!--sql语句片段,将公共部分抽出-->
<sql id="Base_Column_List">
id, name, age,address,create_time, update_time
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_user
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from t_user
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="com.lsqingfeng.springboot.entity.User">
insert into t_user ( name, age,address,create_time ,update_time
)
values (#{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER},#{address},#{createTime,jdbcType=TIMESTAMP},#{updateTime,jdbcType=TIMESTAMP}
)
</insert>
<!--动态sql-->
<insert id="insertSelective" parameterType="com.lsqingfeng.springboot.entity.User">
insert into t_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="name != null">
name,
</if>
<if test="age != null">
age,
</if>
<if test="address != null">
address,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="updateTime != null">
update_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=INTEGER},
</if>
<if test="name != null">
#{name,jdbcType=VARCHAR},
</if>
<if test="age != null">
#{age,jdbcType=INTEGER},
</if>
<if test="address != null">
#{address},
</if>
<if test="updateTime != null">
#{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null">
#{updateTime,jdbcType=TIMESTAMP},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.lsqingfeng.springboot.entity.User">
update t_user
<set>
<if test="name != null">
name = #{name,jdbcType=VARCHAR},
</if>
<if test="age != null">
age = #{age,jdbcType=INTEGER},
</if>
<if test="address != null">
address = #{address},
</if>
<if test="createTime != null">
create_time = #{createTime,jdbcType=TIMESTAMP},
</if>
<if test="age != null">
update_time = #{updateTime,jdbcType=TIMESTAMP}
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.lsqingfeng.springboot.entity.User">
update t_user
set name = #{name,jdbcType=VARCHAR},
age = #{age,jdbcType=INTEGER},
address = #{address,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=TIMESTAMP},
update_time = #{updateTime,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=INTEGER}
</update>
</mapper>
然后在Service层中引用mapper.
2.4 配置Mapper的路径
我们现在是把xml类型的文件都放在了resources下的mapper文件夹中了。但是这个路径对与我们的项目中的mybatis来说他是不知道的,所以我们需要告诉它这个路径的位置。怎么告诉呢,就是在application.yml中配置一下。
代码语言:javascript复制mybatis:
mapper-locations: classpath:mapper/*.xml
mapper的位置位于 classpath下的mapper文件夹中的所有xml结尾的文件。
这里还有一个问题也提下。就是mapper的接口的位置我们并没有配置,他是怎么知道的呢,其实是我们在每个Mapper的接口上都加上了@Mapper的注解,所以他可以自动扫描到。如果要加这个注解,那么所有的Mapper接口上就都需要加,这其实是比较麻烦的。怎么办呢,我们可以都不加这个注解,然后在SpringBoot的启动类上,加上一个MapperScan注解,将mapper的包路径配置进去,就都不用加了。
我们去掉@Mapper注解,也不加任何配置会报错:
我们在主类上添加注解:
代码语言:javascript复制@SpringBootApplication
@MapperScan("com.lsqingfeng.springboot.mapper")
public class SpringBootLearningApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootLearningApplication.class, args);
}
}
再次启动:
成功了。
3、 接口测试
组装好项目后,我们来调用一下接口。继续使用之前的jdbc里写好的Controller进行测试。
调用成功后,观察数据库。
李四这条使我们刚刚插入进去的,只不过两个时间是空值,因为我们并没有给时间赋值。
再看看查询接口。
4、mybatis自动填充时间
上面的案例中,由于我们没有对时间进行设置,导致创建时间和修改时间都是空的。其实Mybatis中为我们提供了拦截器的机制,相当于可以对每次执行的sql进行拦截,这样我们就可以对对操作进行判断,如果是插入操作,就直接设置创建时间和修改时间为当前时间。如果是更新操作则设置更新时间为当前时间。
拦截器代码如下。
代码语言:javascript复制import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Date;
/**
* Mybatis拦截器,用能与设置创建时间和更新时间
*/
@Component
@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
public class CreateTimeSetInterceptor implements Interceptor {
private static final String CREATE_TIME_SETTER = "setCreateTime";
private static final String UPDATE_TIME_SETTER = "setUpdateTime";
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
if (ms.getSqlCommandType() == SqlCommandType.INSERT) {
setTimeIfNeccesary(parameter, CREATE_TIME_SETTER);
setTimeIfNeccesary(parameter, UPDATE_TIME_SETTER);
} else if (ms.getSqlCommandType() == SqlCommandType.UPDATE) {
setTimeIfNeccesary(parameter, UPDATE_TIME_SETTER);
}
return invocation.getMethod().invoke(invocation.getTarget(), invocation.getArgs());
}
private void setTimeIfNeccesary(Object param, String methodName) {
Class<?> cls = param.getClass();
if (cls == ParamMap.class) {
@SuppressWarnings("unchecked")
ParamMap<Object> map = (ParamMap<Object>) param;
map.entrySet().forEach(entry -> {
if (!entry.getKey().equals("et")) {
setIfSetterExist(entry.getValue(), methodName);
}
});
} else {
setIfSetterExist(param, methodName);
}
}
private void setIfSetterExist(Object param, String methodName) {
Class<?> cls = param.getClass();
try {
Method m = null;
try {
m = cls.getDeclaredMethod(methodName, new Class[] { Date.class });
if (m != null) {
m.setAccessible(true);
m.invoke(param, new Date());
}
} catch (NoSuchMethodException e1) {
m = cls.getDeclaredMethod(methodName, new Class[] { Timestamp.class });
if (m != null) {
m.setAccessible(true);
m.invoke(param, new Timestamp(System.currentTimeMillis()));
}
}
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
e.printStackTrace();
}
}
}
接下来我们再来执行一个插入操作》
再次观察数据库:
发现时间已经有值了。
5、 mybatis-generator 插件
我们上面通过User表创建了UserMapper接口和UserMapper的xml实现。其实这类文件中的内容是比较有规律的,所以如果每次没有都去自己写,是比较耗费时间的,所有mybatis为我们提供了mybatis-generator插件,通过这个插件,我们可以设置对应的数据库连接和表名,然后插件就会帮我们自动生成对应的实体,mapper接口和Mapper实现,里面的方法只有常用的增删改查操作。能够大大较少我们操作的工作量。 这个步骤,我们一般称之为 mybatis 逆向生成。 逆向生成的方法比较多,有兴趣的自己去找找吧,这里不想展开了,因为现在随着mybatisPlus的普及,使用mybatis-plus-generator的更多一些。
好了关于SpringBoot集成Mybatis我们就介绍这么多。欢迎大家一起交流,有问题随时留言。
另: 配套项目代码已托管中gitCode: gitcode.net/lsqingfeng/…