以下详细地记录下 SpringBoot 集成 Mybatis 的过程,以及在这个过程中遇到的问题及解决方案。
【开发环境】:
- IDEA-2019.1
- SpringBoot-2.1.1.RELEASE
- MAVEN-3.5.3
- MySQL-5.7
【项目结构图】:
SpringBoot 中 Mybatis 的使用
在 SpringBoot 中使用 Mybatis 需要引入一个 starter:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
【注意】:需要添加版本,否则,下载不下来依赖
而 Mybatis 的开发模式有两种:
- 无配置文件的注解版
- 使用 XML 配置文件
接下来就介绍下这两种开发模式
1. 无配置文件的注解版
通过注解来完成 Mybatis 的功能
1、引入依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
2、添加 application.yml 配置:
# mysql 数据库配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
password: root
username: root
driver-class-name: com.mysql.jdbc.Driver
SpringBoot 会自动加载 spring.datasource.*
的相关配置,数据源就会自动注入到 sqlSessionFactory
中,sqlSessionFactory
会自动注入到 Mapper
中,然后,你一切都不用管了,直接拿起来使用就行。
3、在启动类中添加对 mapper 包扫描 @MapperScan
@MapperScan("com.zzc.mapper") // mapper 包路径
@SpringBootApplication
public class App {
public static void main( String[] args ) {
SpringApplication.run(App.class, args);
}
}
或者直接在 Mapper 类上面添加注解 @Mapper
。建议使用上面那种,不然每个 mapper 加个注解也挺麻烦的
4、开发 Mapper:
package com.zzc.mapper;
public interface UserMapper {
@Select("SELECT * FROM TAB_USER")
@Results({
@Result(property = "sId", column = "id"),
@Result(property = "userName", column = "user_name"),
@Result(property = "userSex", column = "user_sex")
})
List<TabUser> listUsers();
@Select("SELECT * FROM TAB_USER WHERE 1=1 AND ID = #{sId}")
@Results({
@Result(property = "sId", column = "id"),
@Result(property = "userName", column = "user_name"),
@Result(property = "userSex", column = "user_sex")
})
TabUser getUserById(String id);
}
Mapper 中写了两个查询方法,使用了 @Selecct
注解,并将其对应的 SQL 语句写入其中
- @Select:是查询类的注解,所有的查询均使用这个
- @Result:修饰返回的结果集,关联实体类属性和数据库字段一一对应,如果实体类属性和数据库属性名保持一致,就不需要这个属性来修饰
注意,使用#符号和$符号的不同:
// This example creates a prepared statement, something like select * from teacher where name = ?;
@Select(“Select * from teacher where name = #{name}”)
Teacher selectTeachForGivenName(@Param(“name”) String name);
// This example creates n inlined statement, something like select * from teacher where name = ‘someName’;
@Select(“Select * from teacher where name = ‘${name}’”)
Teacher selectTeachForGivenName(@Param(“name”) String name);
实体类:
public class TabUser {
private String sId;
private String userName;
private Integer userSex;
// getter/setter
}
对应的 SQL 表:
CREATE TABLE `tab_user` (
`id` varchar(50) NOT NULL,
`user_name` varchar(255) DEFAULT NULL,
`user_sex` tinyint(3) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
【注意】:这里,我特地将数据库中的字段名和实体类中的属性名没有保持。如:数据库中的 user_name
对应实体类中的 userName
。所以,使用了 @Result
注解,将数据库中的字段名映射到实体类的属性名上去。
5、添加测试类进行单元测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testListUsers() {
List<TabUser> tabUsers = userMapper.listUsers();
System.out.println(tabUsers);
}
@Test
public void testgetUserById() {
String id = "1001";
TabUser tabUser = userMapper.getUserById(id);
System.out.println(tabUser);
}
}
按照这个步骤,注解版的 Mybatis 就集成完毕了,很顺利。
2 配置文件的 XML 版本
xml 版本保持映射文件的老传统,接口层只需要定义空方法,系统会自动根据方法名在映射文件(mapper 文件)中找对应的 Sql
1、引入依赖:
pom 文件和上个版本一样
2、添加 application.yml 配置:
application.yml
文件的配置,下文会逐步地讲解为什么会有这样的配置
# mysql 数据库配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/zzc?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
password: root
username: root
driver-class-name: com.mysql.jdbc.Driver
# mybatis 配置
mybatis:
type-aliases-package: com.zzc.entity # 实体类别名
mapper-locations: classpath:mybatis/mapper/*.xml # mapper 配置文件(必要)
#config-location: classpath:mybatis/mybatis-config.xml # mybatis 配置文件(非必要)
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志输出位置
map-underscore-to-camel-case: true # 驼峰命名
# mapper 层的 sql 打印
logging:
level:
com.zzc.mapper: debug # 设置日志级别
注意:config-location
节点和 configuration
节点不能同时出现,否则,会报错:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is java.lang.IllegalStateException: Property 'configuration' and 'configLocation' can not specified with together
。
-
config-location
:就是指定 Mybatis 的配置文件,对 Mybatis 进行基本的配置 -
configuration
:也可以达到对 Mybatis 进行配置的目的
所以,这两者没有必要同时出现!!
配置 Mapper 文件:
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml # mapper 配置文件(必要)
config-location: classpath:mybatis/mybatis-config.xml # mybatis 配置文件(非必要)
指定了 Mybatis 的映射文件位置及 Mybatis 的配置文件
mybatis-config.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>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer" />
<typeAlias alias="Long" type="java.lang.Long" />
<typeAlias alias="HashMap" type="java.util.HashMap" />
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
<typeAlias alias="ArrayList" type="java.util.ArrayList" />
<typeAlias alias="LinkedList" type="java.util.LinkedList" />
</typeAliases>
</configuration>
这里面就是 Mybatis 的配置文件,就是对 Mybatis 进行基本配置。如:配置别名(如上)、驼峰命名配置
3、编写 Mapper 层代码:
public interface UserMapper {
TabUser getUserById(String id);
}
这里只需要定义接口即可
4、编写 Mapper 映射文件:
UserMapper.xml
:
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zzc.mapper.UserMapper">
<select id="getUserById" parameterType="String" resultType="TabUser">
SELECT
id,user_name,user_sex
FROM TAB_USER
WHERE 1=1
AND id = #{id}
</select>
</mapper>
【注意】:mapper 文件后面有后缀名:.xml
。否则,就会报错:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.zzc.mapper.UserMapper.getUserById
resultType="TabUser"
:resultType
:表示返回的类型,这里是返回一个实体类,类名为:com.zzc.entity.TabUser
。本应该是写类的全限定名,但在 application.yml
文件中进行如下配置:
mybatis:
type-aliases-package: com.zzc.entity # 实体类别名
否则,会报错:Caused by: org.apache.ibatis.type.TypeException: Could not resolve type alias 'TabUser'. Cause: java.lang.ClassNotFoundException: Cannot find class:
5、添加测试类进行单元测试:
跟上述一样。
问题一:SQL 查询有数据,但实体类为 NULL
咱们没有配置驼峰命名时:
mybatis:
configuration:
map-underscore-to-camel-case: true # 驼峰命名
单元测试没有报错,但打印对象信息为 NULL。
可以配置 Mybatis 的 SQL 日志信息:
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志输出位置
logging:
level:
com.zzc.mapper: debug # 设置日志级别
通过日志信息判断 Mybatis 有没有执行报错:
通过日志信息,可以看出:Mybatis 执行成功,但赋值给实体类失败。
为什么会失败?
因为,数据库的字段名和实体类的属性名不对应导致的。
这里有两个解决方案:
- 开启驼峰命名(有限制)
- 使用数据库的别名
AS
驼峰命名:
驼峰命名是有限制的:开启驼峰命名后,它会将数据表中的字段中的下划线 _
做出如下修改:去掉下划线 _
,并将下划线 _
后的第一个字母大写。如下:
数据表 | 实体类 |
user_name | userName |
user_sex | userSex |
开启驼峰命名后,发现只有 userName
、userSex
属性有值,但 sId
没有值。因为数据库中对应的字段为 id
,即使开启了驼峰命名后也没有用。所以,得使用数据库的别名 AS
使用数据库的别名 AS
:
修改 UserMapper.xml
文件中的 SQL:
<select id="getUserById" parameterType="String" resultType="TabUser">
SELECT
id AS sId,user_name,user_sex
FROM TAB_USER
WHERE 1=1
AND id = #{id}
</select>
好了,基于 XML 配置的 Mybatis 查询就到这了。
3. 优化配置文件的 XML 版本
通过上面得知,我们将 SQL
写在了一个 Mapper 文件中。假如,我现在需要给 Mapper 接口再添加一个方法:listUsers()
查询所有用户。那么,我们也得在 Mapper 文件中写 SQL。如下:
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zzc.mapper.UserMapper">
<select id="listUsers" resultType="TabUser">
SELECT
id AS sId,user_name,user_sex
FROM TAB_USER
</select>
<select id="getUserById" parameterType="String" resultType="TabUser">
SELECT
id AS sId,user_name,user_sex
FROM TAB_USER
WHERE 1=1
AND id = #{id}
</select>
</mapper>
通过上面,我们发现:SELECT
后面的字段id AS sId,user_name,user_sex
有重复的,如果方法越多,那么,Mapepr 文件中的这些重复的字段名就会越多。
为了避免这种情况发生,我们可以将重复的东西抽取出来:
<sql id="UserBaseColumnList">
id,user_name,user_sex
</sql>
当然,如果数据表中的字段名与实体类中的属性名不对应,这个 sql
片段中也可以直接设置别名 AS
,从而达到赋值的目的。
但是,在 Mybatis 中,有一个更好的选择 resultMap
,将数据表的字段名与实体类的属性名进行映射:
<resultMap id="UserBaseResultMap" type="TabUser">
<id column="id" property="id" jdbcType="VARCHAR" />
<result column="user_name" property="userName" jdbcType="VARCHAR" />
<result column="user_sex" property="userSex" jdbcType="TINYINT" />
</resultMap>
jdbcType
这个属性名是可以省略的。
那么综合这两点(sql
、resultMpa
标签),最后的 Mapper 文件如下:
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zzc.mapper.UserMapper">
<resultMap id="UserBaseResultMap" type="TabUser">
<id column="id" property="sId" jdbcType="VARCHAR" />
<result column="user_name" property="userName" jdbcType="VARCHAR" />
<result column="user_sex" property="userSex" jdbcType="TINYINT" />
</resultMap>
<sql id="UserBaseColumnList">
id,user_name,user_sex
</sql>
<select id="listUsers" resultMap="UserBaseResultMap">
SELECT
<include refid="UserBaseColumnList" />
FROM TAB_USER
</select>
<select id="getUserById" parameterType="String" resultMap="UserBaseResultMap">
SELECT
<include refid="UserBaseColumnList" />
FROM TAB_USER
WHERE 1=1
AND id = #{id}
</select>
</mapper>
然后,通过单元测试进行测试即可。
4. 总结
4.1 基于 Mybatis 本身
两种模式各有特点:
- 注解版适合简单快速的模式,其实像现在流行的这种微服务模式,一个微服务就会对应一个自已的数据库,多表连接查询的需求会大大的降低,会越来越适合这种模式。
- 老传统模式比适合大型项目,可以灵活的动态生成 Sql ,方便调整 Sql
4.2 基于 Mybatis 使用
- 如果是想查看 SQL 是否有问题,则可以配置日志,进行日志打印
- xml 配置时,
sql
、resultMap
标签可以更好地提高代码的可读性、可维护性