深入浅出springboot2.x(10)整合MyBatis

整合MyBatis框架

    应该说目前java持久层最为主流的技术已经是MyBatis,它比JPA和Hibernate更为简单易用,也更加灵活。在以管理系统为主的时代,Hibernate的模型化有助于系统的分析和建模,重点在于业务模型的分析和设计,属于表和业务模型分析的阶段。现在已经是移动互联网时代,特点是面向公众,相对而言业务比较简单,但是往往网站会拥有大量的用户,面对的问题主要是大数据、高并发和性能问题。因此在这个时代,互联网企业开发的难度主要集中在大数据和性能问题上,所以互联网企业更加关注的是系统的性能和灵活性。所以MyBatis应用非常广泛。

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的配置

    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

Spring Boot整合MyBatis

    在大部分情况下,应该“擦除”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 作废。

你可能感兴趣的:(spring,boot,整合MyBatis)