【笔记】SpringBoot集成Mybatis

1.

使用mysql数据库

建表语句

create table t_user(
    id bigint(20) primary key AUTO_INCREMENT,
    name varchar(50) not null comment'用户名',
    phone varchar(30) not null comment'手机'
)ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

insert into t_user values("1","小明",'15236852365');

代码结构图

2.SpringBoot集成mybatis(单数据源情况)

2.1 准备工作

2.1.1pom.xml文件内容

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.springbootdemo</groupId>
  <artifactId>dbandcache</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
  </parent>


  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>2.2.2</version>
    </dependency>

    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>2.2.2</version>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.8</version>
    </dependency>

    <!--数据库相关依赖包-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!--数据源-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.0.14</version>
    </dependency>

    <!--mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>
    <!--mybatis-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.2.8</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.1.1</version>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

文件主要需要引入与数据库相关的5个包:Spring-boot-starter-jdbc、druid、mysql-connector-java、mybatis、mybatis-spring。

2.1.2 主类代码

package com.springbootdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication
@EnableSwagger2
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

2.1.3模型类User代码

package com.springbootdemo.model;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@ApiModel
public class User {
    @ApiModelProperty("用户id")
    private Long id;
    @ApiModelProperty("用户名")
    private String name;
    @ApiModelProperty("用户电话")
    private String phone;
}

2.1.4 mapper 包下UserMapper代码

package com.springbootdemo.mapper;
import com.springbootdemo.model.User;
public interface UserMapper {
    User selectUserById(Long id);
}

 

2.1.5 UserMapper对应的xml文件,即src/main/resource/mapping/UserMapper.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.springbootdemo.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="com.springbootdemo.model.User">
    <result column="id" jdbcType="BIGINT" property="id" />
    <result column="name" jdbcType="VARCHAR" property="name" />
    <result column="phone" jdbcType="VARCHAR" property="phone" />
  </resultMap>

  <select id="selectUserById" resultMap="BaseResultMap" parameterType="java.lang.Long">
    select id,name,phone from t_user where id=#{id}
  </select>
</mapper>

 

2.1.6在application.properties文件中配置数据信息,及定义数据库连接的4要素

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.238.115:3306/springboot_data?zeroDataTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
jdbc.username=username
jdbc.password=123456

2.2开始集成MyBatis

2.2.1代码如com.springbootdemo.config.MyBatisConfig类所示:

package com.springbootdemo.config;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;


import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@Configuration
@MapperScan(basePackages = "com.springbootdemo.mapper")
public class MyBatisConfig {
    @Autowired
    private Environment env;
    @Bean
    public DataSource dataSource() throws Exception {
        Properties prop=new Properties();
        prop.put("driverClassName",env.getProperty("jdbc.driverClassName"));
        prop.put("url",env.getProperty("jdbc.url"));
        prop.put("username",env.getProperty("jdbc.username"));
        prop.put("password",env.getProperty("jdbc.password"));
        return  DruidDataSourceFactory.createDataSource(prop);
    }


    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
        SqlSessionFactoryBean fb=new SqlSessionFactoryBean();
        fb.setDataSource(ds);//指定数据源
        fb.setTypeAliasesPackage("com.springbootdemo.model");//指定基包
        fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapping/*.xml"));//指定XML文件位置
        return fb.getObject();
    }
}

这个类是整个项目中最重要的类,他替代了常规Spring与mybatis集成时使用的xml文件,用于配置持久层所需要的信息,将dataSource、sessionFactory和Mapper的扫描配置放在这里。

整个类流程如下:

SqlSessionFactoryBean中的TypeAliasesPackage用来指定domain类的基包,即指定在xxxMapper.xml中可以用简名代替全类名;
MapperLocations用来指定xxxMapper.xml文件的所在位置,不过如果mybatis完全使用注解,就不需要设置这两个参数。

2.3根据基本分成架构,测试mybatis集成,在此设计三个类,dao,service和controller

2.3.1 dao类,com.springbootdemo.dao.UserDao代码如下:

package com.springbootdemo.dao;

import com.springbootdemo.mapper.UserMapper;
import com.springbootdemo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class UserDao {
    @Autowired
    private UserMapper userMapper;
    public User selectByPrimaryKey(Long id){
        return userMapper.selectUserById(id);
    }
}

 

2.3.2 service类,com.springbootdemo.service.UserService代码如下:

package com.springbootdemo.service;

import com.springbootdemo.dao.UserDao;
import com.springbootdemo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    
    public User getUser(Long id){
       return userDao.selectByPrimaryKey(id);
    }

}

 

2.3.3 controller类,com.springbootdemo.controller.UserController代码如下:

package com.springbootdemo.controller;

import com.springbootdemo.model.User;
import com.springbootdemo.service.UserService;
import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@Api("用户相关Api")
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;
    @ApiOperation("根据id获取用户信息")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "query",name="id",dataType = "long", required=true,value="用户id",defaultValue = "1")
    })
    @ApiResponses({
            @ApiResponse(code=401,message = "权限校验不通过")
    })
    @RequestMapping(value = "/getUserInfo",method = RequestMethod.GET)
    public User getUserInfo(@RequestParam("id") Long id){
        return userService.getUser(id);
    }
}

至此,完成了整个项目,功能也很简单,就是根据用户id从数据库获取用户信息。

2.4测试效果

3.多数据源集成

      大多数情况下,一个服务只需要一个数据源,但有时也会需要访问多个数据源,比如常见的读写分离就是对两个数据源进行操作。

        假设现在有两个数据源db1和db2,用户信息表t_user存在db1中,用户的车辆信息表t_car存在db2中,dbandcache这个服务需要根据用户的id查询出用户信息的同时也要查询出该用户车辆信息,一起返回给前端,这个时候就出现了需要访问两个数据源的情况了。

下面来完成这个需求

3.1建库建表

建表语句

CREATE TABLE `t_car`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '车名',
  `color` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '颜色',
  `owner` bigint(20) NOT NULL COMMENT '车主',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

INSERT INTO `t_car` VALUES (1, '宝马', 'red', 1);

3.2 Model,Mapper类

3.2.1 com.springbootdemo.model.Car代码

package com.springbootdemo.model;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Car {
    private Long id;
    private String name;
    private String color;
    private Long owner;
}

3.2.2 com.springbootdemo.mapper.CarMapper代码

package com.springbootdemo.mapper;

import com.springbootdemo.model.Car;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface CarMapper {
    List<Car> selectByOwner(@Param("ownerId")Long ownerId);
}

 

3.2.3 src.main.resources.mapping.CarMapper.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.springbootdemo.mapper.CarMapper">
  <resultMap id="BaseResultMap" type="com.springbootdemo.model.Car">
    <result column="id" jdbcType="BIGINT" property="id" />
    <result column="name" jdbcType="VARCHAR" property="name" />
    <result column="color" jdbcType="VARCHAR" property="color" />
    <result column="owner" jdbcType="BIGINT" property="owner" />
  </resultMap>
  
  <select id="selectByOwner" resultMap="BaseResultMap" parameterType="java.lang.Long">
     select * from t_car where owner=#{ownerId}
  </select>
</mapper>

3.3修改application.properties文件配置信息,添加数据源,新增了springboot_data2数据库的配置

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.238.115:3306/springboot_data?zeroDataTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
jdbc.username=username
jdbc.password=123456

jdbc2.driverClassName=com.mysql.cj.jdbc.Driver
jdbc2.url=jdbc:mysql://192.168.238.115:3306/springboot_data2?zeroDataTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
jdbc2.username=username
jdbc2.password=123456

3.4开始配置,结合AbstractRoutingDataSource实现动态数据源

3.4.1 定义一个枚举类com.springbootdemo.config.DatabaseType

package com.springbootdemo.config;

public enum DatabaseType {
    dbdata1,dbdata2
}

该类列出了所有数据源的key,然后需要创建数据源key的持有类com.springbootdemo.config.DatabaseContextHolder

3.4.2创建数据源key的持有类,com.springbootdemo.config.DatabaseContextHolder

package com.springbootdemo.config;

public class DatabaseContextHolder {
    private static final ThreadLocal<DatabaseType> contextHolder =new ThreadLocal<>();
    public static  void setDatabaseType(DatabaseType type){
        contextHolder.set(type);
    }
    public static  DatabaseType getDatabaseType(){
        return contextHolder.get();
    }
}

该类主要用于选择数据源是,将相应的数据源的key设置到contextHolder中,之后对数据源的访问就使用key对应的数据源.

3.4.3  实现动态数据源,创建类DynamicDataSource继承springjdbc的AbstractRoutingDataSource类.

代码如下

package com.springbootdemo.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DatabaseContextHolder.getDatabaseType();
    }
}

该类实现了继承AbstractRoutingDataSource的determineCurrentLookupKey()方法,该方法通过调用数据源key的持有类DatabaseContextHolder的getDatabaseType()方法获取数据源key。

3.4.4构造动态数据源,修改MyBatisConfig类,代码如下

package com.springbootdemo.config;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;


import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@Configuration
@MapperScan(basePackages = "com.springbootdemo.mapper")
public class MyBatisConfig {
    @Autowired
    private Environment env;
    @Bean
    public DataSource db1DataSource() throws Exception {
        Properties prop=new Properties();
        prop.put("driverClassName",env.getProperty("jdbc.driverClassName"));
        prop.put("url",env.getProperty("jdbc.url"));
        prop.put("username",env.getProperty("jdbc.username"));
        prop.put("password",env.getProperty("jdbc.password"));
        return  DruidDataSourceFactory.createDataSource(prop);
    }
    @Bean
    public DataSource db2DataSource() throws Exception {
        Properties prop=new Properties();
        prop.put("driverClassName",env.getProperty("jdbc2.driverClassName"));
        prop.put("url",env.getProperty("jdbc2.url"));
        prop.put("username",env.getProperty("jdbc2.username"));
        prop.put("password",env.getProperty("jdbc2.password"));
        return  DruidDataSourceFactory.createDataSource(prop);
    }
    @Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("db1DataSource")DataSource db1DataSource,@Qualifier("db2DataSource")DataSource db2DataSource){
        Map<Object,Object> targetDataSources=new HashMap<>();
        targetDataSources.put(DatabaseType.dbdata1,db1DataSource);
        targetDataSources.put(DatabaseType.dbdata2,db2DataSource);
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);//该方法是AbstractRoutingDataSource的方法
        dataSource.setDefaultTargetDataSource(db1DataSource);//默认的dataSource设置为myTestDbDataSource
        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
        SqlSessionFactoryBean fb=new SqlSessionFactoryBean();
        fb.setDataSource(ds);//指定数据源
        fb.setTypeAliasesPackage("com.springbootdemo.model");//指定基包
        fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapping/*.xml"));//指定XML文件位置
        return fb.getObject();
    }
}

在该类中首先创建了两个数据源db1DataSource和db2DataSource,然后将这两个数据源设置到DynamicDataSource数据源中,在DynamicDataSource设置了目标数据源map,并设置默认数据源为db1DataSource,这样就不需要为访问db1DataSource的dao类选择数据源了。对于db2DataSource的访问dao则需要显示指定。

注意:MyBatisConfig类中的三个数据源都是javax.sql.DataSource的子类,所以在DynamicDataSource类上加入@Primary注解,作用是在用一个接口有多个实现类可以注入的时候,默认选择一个,以防Spring因为有多个选择而不知道注入哪个导致报错。

3.5根据分层,配置dao,service,controller

3.5.1 创建model用于获取用户和车辆信息

package com.springbootdemo.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
@NoArgsConstructor
public class UserAndCar extends User{
    private List<Car> cars;

}

3.5.2 创建dao类,com.springbootdemo.dao.CarDao

package com.springbootdemo.dao;

import com.springbootdemo.config.DatabaseContextHolder;
import com.springbootdemo.config.DatabaseType;
import com.springbootdemo.mapper.CarMapper;
import com.springbootdemo.model.Car;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class CarDao {
    @Autowired
    private CarMapper carMapper;

    public List<Car> selectByOwnerId(Long id){
        //操作数据库2的数据时,先选择对应的数据源在操作
        DatabaseContextHolder.setDatabaseType(DatabaseType.dbdata2);
        return carMapper.selectByOwner(id);
    }
}

注意:该类在调用Mapper操作数据库之前,要先选择数据源,即加上 DatabaseContextHolder.setDatabaseType(DatabaseType.dbdata2);语句

3.5.3 修改UserService代码,

package com.springbootdemo.service;

import com.springbootdemo.dao.CarDao;
import com.springbootdemo.dao.UserDao;
import com.springbootdemo.model.Car;
import com.springbootdemo.model.User;
import com.springbootdemo.model.UserAndCar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private CarDao carDao;

    public User getUser(Long id){
       return userDao.selectByPrimaryKey(id);
    }

    public UserAndCar getUserAndCar(Long userId){
        UserAndCar userAndCar = new UserAndCar();
        User user = userDao.selectByPrimaryKey(userId);
        if(user!=null){
            List<Car> cars = carDao.selectByOwnerId(userId);
            userAndCar.setId(user.getId());
            userAndCar.setName(user.getName());
            userAndCar.setPhone(user.getPhone());
            userAndCar.setCars(cars);
        }
        return userAndCar;
    }
}

3.5.4 创建Controller,com.springbootdemo.controller.DbAndCacheController

package com.springbootdemo.controller;

import com.springbootdemo.model.UserAndCar;
import com.springbootdemo.service.UserService;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Api("用户信息和车辆信息相关")
@RestController
@RequestMapping("/userAndCar")
public class DbAndCacheController {
    @Autowired
    private UserService userService;

    @ApiOperation("根据用户ID获取用户及车辆信息")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "query",name = "id",dataType = "long",required=true,value = "用户id",defaultValue = "1")
    })
    @ApiResponses({
            @ApiResponse(code = 401,message = "权限校验不通过")
    })
    @RequestMapping(value="/getUserAndCars",method = RequestMethod.GET)
    public UserAndCar getUserAndCar(@RequestParam("id") long id){
        return userService.getUserAndCar(id);
    }
}

至此,实现了服务中对多数据源的使用。

3.6测试效果

3.7 使用Spring aop简化数据源选择功能

当服务中dao很多,dao中的方法也很多的时候,就发现每次都要写一遍选择数据源的语句。使用Aop便可简化选择功能

创建切面类com.springbootdemo.aspect.DataSourceAspect,代码如下

package com.springbootdemo.aspect;

import com.springbootdemo.config.DatabaseContextHolder;
import com.springbootdemo.config.DatabaseType;
import com.springbootdemo.dao.CarDao;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAspect {
    /**
     * 该类拦截dao包下的所有类的所有方法,
     * 将其中CarDao类的数据源设为db2,
     * 这样就免除了在CarDao类中每个方法都要加入选择数据源语句
     * @param point
     */
    @Before("execution(* com.springbootdemo.dao.*.*(..))")
    public void setDataSourceKey(JoinPoint point){
        //如果有其他类也是使用dbdata2获取使用其他数据源,加条件判断
        if(point.getTarget() instanceof CarDao){
            DatabaseContextHolder.setDatabaseType(DatabaseType.dbdata2);
        }
    }
}

通过该类拦截dao包下的所有类和方法,将carDao类数据源设置为db2,就可以将CarDao中的DatabaseContextHolder.setDatabaseType(DatabaseType.dbdata2);删除了。

至此,SpringBoot集成Mybatis来实现多数据源就完成了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值