应该说目前java持久层最为主流的技术已经是MyBatis,它比JPA和Hibernate更为简单易用,也更加灵活。在以管理系统为主的时代,Hibernate的模型化有助于系统的分析和建模,重点在于业务模型的分析和设计,属于表和业务模型分析的阶段。现在已经是移动互联网时代,特点是面向公众,相对而言业务比较简单,但是往往网站会拥有大量的用户,面对的问题主要是大数据、高并发和性能问题。因此在这个时代,互联网企业开发的难度主要集中在大数据和性能问题上,所以互联网企业更加关注的是系统的性能和灵活性。所以MyBatis应用非常广泛。
MyBatis的官方定义为:MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以对配置和原生Map使用简单的xml或注解,将接口和java的pojo映射成数据库中的记录。
从这个官方定义可以看出,MyBatis是基于一种SQL到pojo的模型,它需要我们提供SQL、映射关系(xml或者注解,目前以xml为主)和pojo。但是对于SQL和pojo的映射关系,它提供了自动映射和驼峰映射等,使开发者的开发工作大大减少;由于没有屏蔽SQL,这对于追求高响应和性能的互联网系统是十分重要的,因此我们可以尽可能地通过SQL去优化性能,也可以做少量的改变以适应灵活多变的互联网应用。与此同时,它还能支持动态SQL,以适应需求的变化。
MyBatis的配置文件包括两大部分,基础配置文件和映射文件。在MyBatis中也可以使用注解来实现映射,只是由于功能和可读性的限制,在实际工作中使用的比较少。spring本身是不支持MyBatis的,所以在spring的项目中都没有考虑MyBatis的整合。但是MyBatis社区为了整合spring自己开发了相应的开发包,因此在springboot中我们可以依赖MyBatis社区提供stater。在maven中加入依赖包:
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.1
MyBatis是一个基于SqlSessionFactory构建的框架。对于SqlSessionFactory而言,它的作用是生成SqlSession接口对象,这个接口对象是MyBatis操作的核心,而在MyBatis-spring的结合中甚至可以“擦除”这个对象,使其在代码中“消失”,这样做的意义是重大的,因为SqlSession是一个功能性的代码,“擦除”它之后,就剩下了业务代码,这样就可以使得代码更具可读性。因为SqlSessionFactory的作用是单一的,只是为了创建核心接口SqlSession,所以在MyBatis应用的生命周期中理当只存在一个SqlSessionFactory对象,并且往往会使用单例模式。而构建SqlSessionFactory是通过配置类来完成的,因此对于mybatis-spring-boot-starter,它会给予我们在配置文件进行Configuration配置的相关内容。
我们先来看一个简单的例子:
实体类:User(对应的表建表语句可以在上一节开头找)
package com.example.mybatisdemo;
import org.apache.ibatis.type.Alias;
@Alias(value="user")//MyBatis指定别名
public class User {
private int id;
private String userName;
private SexEnum sex;
private String note;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public SexEnum getSex() {
return sex;
}
public void setSex(SexEnum sex) {
this.sex = sex;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
这里加入了注解@Alias,并且指定它的别名“user”。同时这里面的属性多了一个性别枚举,在mybatis体系中,枚举是可以通过typeHandler进行转换的。
枚举类SexEnum:
package com.example.mybatisdemo;
public enum SexEnum {
MALE(1,"男"),FEMALE(2,"女");
private int id;
private String name;
SexEnum(int id, String name){
this.id=id;
this.name=name;
}
public static SexEnum getEnumById(int id){
for(SexEnum sex: SexEnum.values()){
if (sex.getId()==id){
return sex;
}
}
return null;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
转换类SexTypeHandler:
package com.example.mybatisdemo;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class SexTypeHandler extends BaseTypeHandler {
//设置非空性别参数
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, SexEnum sexEnum, JdbcType jdbcType) throws SQLException {
preparedStatement.setInt(i,sexEnum.getId());
}
//通过列名读取性别
@Override
public SexEnum getNullableResult(ResultSet resultSet, String col) throws SQLException {
int sex =resultSet.getInt(col);
if(sex !=1 && sex!=2){
return null;
}
return SexEnum.getEnumById(sex);
}
//通过下标读取性别
@Override
public SexEnum getNullableResult(ResultSet resultSet, int i) throws SQLException {
int sex = resultSet.getInt(i);
if(sex !=1 && sex!=2){
return null;
}
return SexEnum.getEnumById(sex);
}
//通过存储过程读取性别
@Override
public SexEnum getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
int sex = callableStatement.getInt(i);
if(sex !=1 && sex!=2){
return null;
}
return SexEnum.getEnumById(sex);
}
}
在MyBatis中对于TypeHandler的要求是实现TypeHandler接口,而它自身为了更加方便也通过抽像类BaseTypeHandler实现了TypeHandler接口,所以这里直接继承抽像类BaseTypeHandler就可以了。注解@MappedJdbcTypes声明JdbcType为数据库的整型,@MappedTypes声明JdbcType为SexEnum,这样MyBatis即可将对应的数据类型进行转换。为了使这个pojo能够跟数据库的数据对应,还需要提供一个映射文件
映射文件目录结构:resources\mybatis\userMapper.xml
定义MyBatis操作接口:
package com.example.mybatisdemo;
import org.springframework.stereotype.Repository;
@Repository
public interface MyBatisUserDao {
public User getUser(Integer id);
}
我们还需要对映射文件,pojo的别名和typeHandler进行配置
#MyBatis映射文件
mybatis.mapper-locations=classpath:mybatis/*.xml
#MyBatis扫描别名包,和注解@Alias联用
mybatis.type-aliases-package=com.example.mybatisdemo
#配置typeHandler的扫描包
mybatis.type-handlers-package=com.example.mybatisdemo
在大部分情况下,应该“擦除”SqlSession接口的使用而直接获取Mapper接口,这样就更加集中于业务的开发,而不是MyBatis功能性的开发。但是在上面我们可以看到Mapper是一个接口,是不可以使用new为期生成对象实例的。为了方便我们使用,MyBatis社区在与Spring整合的包中提供了两个类,MapperFactoryBean和MapperScannerConfigure。MapperFactoryBean是针对一个接口配置,而MapperScannerConfigure则是扫描装配,也就是提供扫描装配MyBatis的接口到springioc容器中。实际上,MyBatis还提供了注解@MapperScan,也能够将MyBatis所需的对应接口扫描装配到springioc容器中。相对于MapperFactoryBean和MapperScannerConfigure这样需要代码开发的方式,@MapperScan显得更为简单,所以在大部分的情况下,建议使用这个。下面分别对这三个使用给与说明。
使用MapperFactoryBean配置MyBatisUserDao接口,我们现在spring boot启动类中加入这段代码:
@Autowired
SqlSessionFactory sqlSessionFactory;
@Bean
public MapperFactoryBean initMyBatisUserDao(){
MapperFactoryBean bean = new MapperFactoryBean<>();
bean.setMapperInterface(MyBatisUserDao.class);
bean.setSqlSessionFactory(sqlSessionFactory);
return bean;
}
这里的SqlSessionFactory 是spring boot自动生成的,可以直接使用MapperFactoryBean定义Mapper接口。
然后是服务接口和实现类:
package com.example.mybatisdemo;
public interface MyBatisUserService {
public User getUser(Integer id);
}
package com.example.mybatisdemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MyBatisUserServiceImpl implements MyBatisUserService {
@Autowired
MyBatisUserDao myBatisUserDao;
@Override
public User getUser(Integer id) {
return myBatisUserDao.getUser(id);
}
}
使用控制器测试:
package com.example.mybatisdemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/mybatis")
public class MyBatisController {
@Autowired
MyBatisUserService myBatisUserService;
@RequestMapping("/getUser")
@ResponseBody
public User gerUser(Integer id){
return myBatisUserService.getUser(id);
}
}
启动spring boot应用,在浏览器输入http://localhost:8080/mybatis/getUser?id=1,就可以看到测试结果。需要在表里面插入id=1的数据。
使用MapperScannerConfigure类来定义扫描,它可以配置包和注解(或者接口)类型进行装配,首先要删除spring boot启动文件中使用MapperFactoryBean配置MyBatisUserDao接口的代码,然后加入MapperScannerConfigure扫描装配MyBatis接口:
/**
* 配置mybatis接口扫描
* 返回扫描器
*/
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
//定义扫描器实例
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
//加载SqlSessionFactory,spring boot会自动生成SqlSessionFactory实例
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
//定义扫描的包
mapperScannerConfigurer.setBasePackage("com.example");
//限定被标注@Repository的接口才被扫描
mapperScannerConfigurer.setAnnotationClass(Repository.class);
//通过继承某个接口限制扫描,一般使用不多
//mapperScannerConfigurer.setMarkerInterface();
return mapperScannerConfigurer;
}
上述代码中使用MapperScannerConfigure类定义了扫描的包,这样程序就会去扫描对应的包了。还是用了注解限制,限制被标注为Repository,这样就防止在扫描中被错误装配。
实际上还有更为简单的方式,那就是注解@MapperScan,把MapperFactoryBean和MapperScannerConfigure相关代码删除,然后在启动类上加入如下代码:
@EnableJpaRepositories(basePackages = "com.example")
@EntityScan(basePackages = "com.example")
@SpringBootApplication
@ComponentScan({"com.example"})
@MapperScan(basePackages = "com.example.*",
sqlSessionFactoryRef = "sqlSessionFactory",
sqlSessionTemplateRef="sqlSessionTemplate",
annotationClass = Repository.class)
public class JpAdemoApplication {
.......
}
@MapperScan允许我们通过扫描加载MyBatis的Mapper,如果你的springboot项目中不存在多个SqlSessionFactory(或者SqlSessionTemplate),那么你完全可以不配置sqlSessionFactoryRef 或sqlSessionTemplateRef。但是如果有多个时,就需要我们指定了,而且有一点需要注意的:sqlSessionTemplateRef的优先权是大于sqlSessionFactoryRef 的,也就是当我们将两者都配置后,系统会优先选择sqlSessionTemplateRef,而把sqlSessionFactoryRef 作废。