我在之前的项目中使用的都是spring data jpa,开发起来十分快速。几个标签注解一加,repository一继承,一个基本的增删改查就有了。缺点就是写比较复杂的SQL的时候不够灵活,只能以HQL的形式写在注解@Query里,如果碰上子查询, union,之类的复杂查询代码阅读起来就很费力。
在新项目里,采用了mybatis作为ORM框架,此文记录一下我在初次接触mybatis中踩的众多坑中的一部分,同时也作为一个知识点总结梳理。
项目结构
先构建了一个maven项目,pom文件:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
mybatis-spring-boot-starter
此包为mybatis的springboot启动包,里面包含了大量的默认配置和mybatis的核心类,没有去细看里面有什么了spring-boot-starter 与 spring-boot-starter-web
是springboot基础包,提供基本web功能mysql-connector-java
我用的是mysql,如果用oracle的需要注意一下包文件不能直接下的。
文件结构:
项目配置
mybatis 可以使用传统的xml进行配置也可以使用注解。注解和xml各有各的优势,当然也可以混合起来使用。
使用注解进行配置
首先找到springboot的配置文件,我用的是application.yml
server:
port: 9090
address: localhost
spring:
datasource:
jdbc-url: jdbc:mysql://localhost:3306/boot?useUnicode=true&characterEncoding=utf8&useSSL=true
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
mybatis:
typeAliasesPackage: com.boot.domain
config-locations: classpath:com/boot/config/mybatis/mybatis-config.xml
typeAliasesPackage
对应实体类所在的包,会自动获取包中的简单类的名字的别名,多个不同路径的包可以用逗号或者冒号号分割,需要写全类名。这个别名是在查询中parameterType 参数类型用的,如果没有配别名就需要写为parameterType="cn.core.bean.xxx"。 也可以 用@Alias(article)手动设置别名。config-locations
这是本地配置文件路径,这里的classpath是指打包后的classes目录(图我放最后了)。我这个配置文件只配置了一些基础类的映射如
<typeAlias alias="Integer" type="java.lang.Integer" />
- spring: datasource: jdbc-url
这里有个坑,之前的都是url,现在要用jdbc-url不然会数据源报错,原因未知。可能的原因
在主类xxxApplication的main方法上加个标签@MapperScan("com.xxx.mapper")
@SpringBootApplication
@MapperScan("com.boot.mapper")
@EnableTransactionManagement
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- @MapperScan
这个标签会扫描指定目录下的mapper文件,同样可以用逗号隔开配置多个。但是如果是需要扫描动态路径的话就蛋疼了,需要自定义一个MapperScanner,这里有篇参考文章SpringBoot 为MapperScan添加动态扫描(占位符)功能 - @EnableTransactionManagement
开启springboot 事务支持
实体的mapper类接口
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user")
@Results({
@Result(property = "id", column = "id", javaType = Long.class),
@Result(property = "name", column = "name", javaType = String.class),
@Result(property = "sex", column = "sex")
})
List<User> getAll();
@Select("Select * from user where name = #{name}")
User findUserByName(@Param("name") String name);
}
简单的实体类
public class User {
Long id;
String name;
String sex;
//省略了getter与setter
}
到此就没有mybatis的事了,再上一个简单的mvc结构
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAll() {
return userMapper.getAll();
}
}
这里有个问题,mapper接口是什么时候被spring容器接管的,为什么可以直接@Autowired 把UserMapper 进行注入?在最初的时候,配置使用的是xml形式,这里很明显是定义成了一个bean,在spring启动的时候被加载进容器。
<bean id="articleMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.boot.mapper.ArticleMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
现在使用@Mapper就可以。但是每个都这么加有点麻烦的,所以main方法上的@MapperScan 包扫描帮你解决了这个问题。
写个controller测试一下
@RestController
@RequestMapping("/article")
public class ArticleController {
@Autowired
private ArticleService articleService;
@GetMapping("/getAll")
public String getAll(){
List<Article> users = articleService.findAll();
return String.valueOf(users.size());
}
}
启动程序,测试一下
2018-06-19 12:29:41.923 DEBUG 9668 --- [0.1-9090-exec-8] com.boot.mapper.UserMapper.getAll : ==> Preparing: SELECT * FROM user
2018-06-19 12:29:41.924 DEBUG 9668 --- [0.1-9090-exec-8] com.boot.mapper.UserMapper.getAll : ==> Parameters:
2018-06-19 12:29:41.925 DEBUG 9668 --- [0.1-9090-exec-8] com.boot.mapper.UserMapper.getAll : <== Total: 1
OK,正常查询。基于注解来进行查询直接把烦人的xml文件给干掉了,缺点就是语句不能太复杂,否则一旦SQL多起来,代码很难阅读。
使用配置文件进行配置
虽然是使用配置文件,但是也不会完全不用注解。在main方法上面任然是添加@MapperScan标签。
article简单的实体类
public class Article {
Long id;
String title;
String author;
String content;
//todo getter and setter
}
实体类的mapper接口很干净的方法定义。
public interface ArticleMapper {
List<Article> findAll();
}
之后写一个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.boot.mapper.ArticleMapper" >
<resultMap id="BaseResultMap" type="com.boot.domain.Article" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="title" property="title" jdbcType="VARCHAR" />
<result column="author" property="author" jdbcType="VARCHAR" />
<result column="content" property="content" jdbcType="VARCHAR" />
</resultMap>
<sql id="Base_Column_List" >
id, title, author, content
</sql>
<select id="findAll" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM article
</select>
</mapper>
如何实现mapper与xml的对应呢。<mapper namespace="com.boot.mapper.ArticleMapper" > namespace属性就是来绑定mapper接口的,绑定接口后,可以不用写接口实现类,mybatis会通过该绑定自动帮你找到对应要执行的SQL语句。
好了,坑的地方来了,spring并没有接管xml文件,他可找不到xml对应的mapper,但是依照习惯优于配置的原则,mapper和xml在同一目录下,他们就能够正常对应的。其实这个xml的文件位置可以有多种形式
1.直接写在mapper文件的同级目录下,但是我们知道maven打包的时候java目录下的xml文件是会被忽略的,所以需要配置一下pom文件的<build>。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</build>
2.第二种。既然java目录下的xml不会被打包进去,那么把xml写在resource文件下不就行了吗。于是在resource下创建与mapper一致的目录结构。目的就是为了让打包后xml与mapper在一个目录下。
3.第三种。指定xml的文件路径,这里就要配点东西了,我以标签的形式加个配置
@Configuration
@MapperScan(basePackages = "com.boot.mapper", sqlSessionTemplateRef = "SqlSessionTemplate")
public class MybatisConfig {
@Bean(name = "MybatisDS")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource DataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "SqlSessionFactory")
public SqlSessionFactory SqlSessionFactory(@Qualifier("MybatisDS") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:com/boot/mapper/imp/*.xml"));
return bean.getObject();
}
@Bean(name = "TransactionManager")
public DataSourceTransactionManager TransactionManager(@Qualifier("MybatisDS") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "SqlSessionTemplate")
public SqlSessionTemplate SqlSessionTemplate(@Qualifier("SqlSessionFactory") SqlSessionFactory sqlSessionFactory)
throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
标签与xml没有什么区别。
@Bean(name = "MybatisDS")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource DataSource() {
return DataSourceBuilder.create().build();
}
这个是springboot里封装的jdbc的数据源,简单看一下,在无参的情况下生成了一个com.zaxxer.hikari.HikariDataSource数据源,嗯嗯~没什么研究,貌似是一个最新的高效率数据源,成功的干死了c3p0与tomcat自带的jndi。
接下来是mybatis的核心SqlSessionFactory,这个东西可以看看官网怎么说的:
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。
官网很详细的介绍了这个,链接在这里
我这个写法有点丑,SqlSessionFactory里面setMapperLocations方法指定路径即可。
TransactionManager是spring的事务管理,这里让spring的事务管理接管一下。
SqlSessionTemplate也是mybatis的核心类。这个类负责管理MyBatis的SqlSession,调用MyBatis的SQL方法
这样配好之后mapper接口就可以找到对应的实现xml文件了。
这是一张打包好的项目结构图:
其实最简单的就是第二种方法,但是我就是想折腾一下。
此文,简单的回顾了一下我用springBoot与mybatis整合的过程,文件的两种配置方式和mapper/xml映射的基本原理。 还留了很多坑,比如mybatis缓存等等。。在之后会补上的
参考资料:
mybatis官方简介
关于sqlSessionTemplate
Mybatis SqlSessionTemplate 源码解析
Spring Boot 实践折腾记(三):三板斧,Spring Boot下使用Mybatis 强烈推荐
spring mybatis mapper接口注解方式注入
@Mappe与@MapperScan关系
spring是怎样管理mybatis的及注入mybatis mapper bean的
感谢前辈们的分享,有什么问题欢迎在评论区指出。