SpringBoot31 整合SpringJDBC、整合MyBatis、利用AOP实现多数据源

一、整合SpringJDBC

1  JDBC

  JDBC(Java Data Base Connectivity,Java 数据库连接)是一种用于执行 SQL 语句的 Java API,可以为多种关系数据库提供统一访问,它一组用 Java 语言编写的类和接口组成。JDBC 提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

  1.1 优点

    JDBC 就是一套 Java 访问数据库的 API 规范,利用这套规范屏蔽了各种数据库 API 调用的差异性;

    当 Java 程序需要访问数据库时,直接调用 JDBC API 相关代码进行操作,JDBC 调用各类数据库的驱动包进行交互,最后数据库驱动包和对应的数据库通讯,完成 Java 程序操作数据库。

  1.2 缺点

    直接在 Java 程序中使用 JDBC 比较复杂,需要 7 步才能完成数据库的操作:

      加载数据库驱动 -> 建立数据库连接 -> 创建数据库操作对象 -> 编写SQL语句 -> 利用数据库操作对象执行数据库操作 -> 获取并操作结果集 -> 关闭连接对象和操作对象,回收资源

    利用JDBC操作数据库参考博文

  1.3 新技术

    由于JDBC操作数据库非常复杂,所以牛人们编写了很多ORM框架,其中Hibernate、Mybatis、SpringJDBC最流行;

    时间又过了N年,

    又有牛人在Hibernate的基础上开发了SpringDataJPA,在Mybatis的基础上开发出了MybatisPlus。

2 SpringBoot集成SpringJDBC环境搭建

  2.1 创建一个SpringBoot项目

    引入 spring-boot-starter-web 、spring-boot-starter-jdbc、mysql-connector-java 者三个主要依赖;

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
必要依赖

    再引入 devtools、lombok这两个辅助依赖。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
辅助依赖

    2.1.1 依赖说明   

<?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.xunyji</groupId>
    <artifactId>spring_jdbc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring_jdbc</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>
pom.xml

      查看pom.xml依赖图可以知道 spring-boot-starter-jdbc 依赖了spring-jdbc、HikariCP;

      spring-jdbc主要提供JDBC操作相关的接口,HikariCP就是传说中最快的连接池。

  2.2 数据库准备

    2.2.1 创建数据表

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(32) DEFAULT NULL COMMENT '用户名',
  `password` varchar(32) DEFAULT NULL COMMENT '密码',
  `age`  int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
users

    2.2.1 配置数据源信息

      坑:Spring Boot 2.1.0 中,com.mysql.jdbc.Driver 已经过期,推荐使用 com.mysql.cj.jdbc.Driver。

      技巧:IDEA是可以连接数据库的哟,而且还可以反向生成对应的实体类哟;IDEA连接数据库并生成实体列参考博文

spring.datasource.url=jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=****
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

    2.2.3 创建实体类

      版案例使用了lombok进行简化编写

package com.xunyji.spring_jdbc.model.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 王杨帅
 * @create 2018-11-18 10:43
 * @desc
 **/
@Data // 自动生成get、set、toString、equals、hashCode、canEaual方法 和 显示无参构造器
@Builder // 生成builder方法
@NoArgsConstructor // 生成无参构造器
@AllArgsConstructor // 自动生成所有字段的有参构造器,会覆盖无参构造器
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}
User.java

  2.3 编写持久层

    2.3.1 持久层接口

package com.xunyji.spring_jdbc.repository;

import com.xunyji.spring_jdbc.model.entity.User;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author 王杨帅
 * @create 2018-11-18 10:53
 * @desc user表对应的持久层接口
 **/
public interface UserRepository {
    Integer save(User user);
    Integer update(User user);
    Integer delete(Integer id);
    List<User> findAll();
    User findById(Integer id);
}
UserRepository.java

    2.3.2 持久层实现类

      技巧:在实现类中依赖注入 JdbcTemplate,它是Spring提供的用于JDBC操作的工具类。

    @Autowired
    private JdbcTemplate jdbcTemplate;
package com.xunyji.spring_jdbc.repository.impl;

import com.xunyji.spring_jdbc.comm.exception.ExceptionEnum;
import com.xunyji.spring_jdbc.comm.exception.FuryException;
import com.xunyji.spring_jdbc.model.entity.User;
import com.xunyji.spring_jdbc.repository.UserRepository;
import com.xunyji.spring_jdbc.repository.impl.resultmap.UserRowMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author 王杨帅
 * @create 2018-11-18 10:55
 * @desc user表对应的持久层实现类
 **/
@Repository
public class UserRepositoryImpl implements UserRepository {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Boolean save(User user) {
        Integer saveResult = jdbcTemplate.update(
                "INSERT user (name, age, email) VALUES (?, ?, ?)",
                user.getName(), user.getAge(), user.getEmail()
        );
        if (saveResult.equals(1)) {
            return true;
        }
        return false;
    }

    @Override
    public Boolean update(User user) throws Exception {
        Integer updateResult = jdbcTemplate.update(
                "UPDATE user SET name = ?, age = ?, email = ? WHERE id = ?",
                user.getName(), user.getAge(), user.getEmail(), user.getId()
        );
        if (updateResult.equals(1)) {
            return true;
        }
        return false;
    }

    @Override
    public Boolean delete(Long id) {
        Integer deleteResult = jdbcTemplate.update(
                "DELETE FROM user WHERE id = ?",
                id
        );
        if (deleteResult.equals(1)) {
            return true;
        }
        return false;
    }

    @Override
    public List<User> findAll() {
        return jdbcTemplate.query(
                "SELECT * FROM user",
                new UserRowMapper()
        );
    }

    @Override
    public User findById(Long id) {
        try {
            User user = jdbcTemplate.queryForObject(
                    "SELECT id, name, age, email FROM user WHERE id = ?",
                    new Object[]{id},
                    new BeanPropertyRowMapper<>(User.class)
            );
            return user;
        } catch (EmptyResultDataAccessException e) {
            log.info(e.getMessage());
            throw new FuryException(ExceptionEnum.RESULT_IS_EMPTY);
        }
    }
}
UserRepositoryImpl .java

      技巧:新增、更新、删除都是利用JdbcTemplate的update方法,update方法的返回值是执行成功的记录数(需在实现类中根据结果判断是否操作成功);

      技巧:利用JdbcTemplate的queryForObject方法查询单条记录,利用JdbcTemplate的query查询多条记录;

      技巧:利用JdbcTemplate的queryForObject方法查询单条记录时如果查询不到就会抛出EmptyResultDataAccessException,需要进行捕获。

      技巧:查询记录时需要对结果集进行封装,可以直接利用BeanPropertyRowMapper实例进行封装,或者自定义一个实现了UserRowMapper的实现类

    2.3.3 持久层测试类

package com.xunyji.spring_jdbc.repository.impl;

import com.xunyji.spring_jdbc.model.entity.User;
import com.xunyji.spring_jdbc.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

import static org.junit.Assert.*;

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class UserRepositoryImplTest {


    @Autowired
    private UserRepository userRepository;

    private User user;

    @Before
    public void init() {
        user = User.builder()
                .id(81L)
                .age(33)
                .name("warrior")
                .email("cqdzwys@163.com")
                .build();
    }

    @Test
    public void save() throws Exception {
        Boolean saveNumber = userRepository.save(user);
        log.info("新增结果为:" + saveNumber);
    }

    @Test
    public void update() throws Exception {
        Boolean updateResult = userRepository.update(user);
        log.info("更新结果为:" + updateResult);
    }

    @Test
    public void delete() throws Exception {
        Boolean delete = userRepository.delete(81L);
        log.info("删除结果为:" + delete);
    }

    @Test
    public void findById() throws Exception {
        User byId = userRepository.findById(8L);
        log.info("获取的数据信息为:" + byId);
    }

    @Test
    public void findAll() throws Exception {
        List<User> all = userRepository.findAll();
        log.info("获取到的列表数据为:" + all);
        all.stream().forEach(System.out::println);
    }

}
UserRepositoryImplTest.java

  2.4 编写服务层

    待更新...

  2.5 编写控制层

    待更新...

3 多数据源

  3.1 硬编码实现【不推荐】

    思路:配置多个数据源 -> 通过配置读取两个数据源 -> 利用读取到数据源分别创建各自的JdbcTemplate对应的Bean并交给Spring容器管理 -> 在调用持久层中的方法时动态传入JdbcTemplate实例

    3.1.1 配置多个数据源

      技巧:SpringBoot2.x 默认的数据库驱动为  com.mysql.cj.jdbc.Driver,默认的数据库连接池为 HikariCP 

      技巧:HikariCP 连接池读取数据源url时是通过 jdbc-url 获取的

spring:
  datasource:
    primary:
      jdbc-url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: root
      password: 182838
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      jdbc-url: jdbc:mysql://localhost:3306/testdemo2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: root
      password: 182838
      driver-class-name: com.mysql.cj.jdbc.Driver
application.yml

    3.1.2 读取配置的数据源

      技巧:ConfigurationProperties用于方法上时会自动读取配置文件中的值并设置到该方法的返回对象上

      技巧:@Primary 注解的作用是当依赖注入有多个类型相同的Bean时,添加了@Primary的那个Bean会默认被注入

    3.1.3 创建JdbcTemplate

    3.1.4 配置类代码汇总

package com.xunyji.spring_jdbc_multi_datasource01.comm.datasource;

import com.xunyji.spring_jdbc_multi_datasource01.model.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

/**
 * @author 王杨帅
 * @create 2018-11-18 16:17
 * @desc 多数据源配置
 **/
@Configuration
@Slf4j
public class DataSourceConfig {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public JdbcTemplate primaryJdbcTemplate(
            @Qualifier(value = "primaryDataSource") DataSource dataSource
    ) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public JdbcTemplate secondaryJdbcTemplate(
            @Qualifier(value = "secondaryDataSource") DataSource dataSource
    ) {
        return new JdbcTemplate(dataSource);
    }


}
DataSourceConfig.java

    3.1.5 缺点

      这种硬编码方式实现多数据源时,需要在调用持久层方法时指定动态的JdbcTemplate

  3.2 AOP实现【推荐】

    思路:配置多个数据源 -> 读取数据源信息 -> 利用AbstractRoutingDataSource抽象类配置数据源信息 -> 利用AOP实现动态数据源切换

    3.2.1 AOP知识点

      参考博文

    3.2.2 配置多个数据源

spring:
  datasource:
    primary:
      jdbc-url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: root
      password: 182838
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      jdbc-url: jdbc:mysql://localhost:3306/testdemo2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: root
      password: 182838
      driver-class-name: com.mysql.cj.jdbc.Driver
application.yml

    3.2.3 创建AbstractRoutingDataSource实现类

package com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce;

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

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

/**
 * @author 王杨帅
 * @create 2018-11-18 21:18
 * @desc 动态数据源
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public DynamicDataSource(
            DataSource defaultTargetDataSource,
            Map<String, DataSource> targetDataSources
    ) {
        // 01 通过父类设置默认数据源
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        // 02 通过父类设置数据源集合
        super.setTargetDataSources(new HashMap<>(targetDataSources));
        // 03 通过父类对数据源进行解析 https://blog.csdn.net/u011463444/article/details/72842500
        super.afterPropertiesSet();
    }

    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        // 获取数据源,如果没有指定,则为默认数据源
        return getDataSource();
    }

    /**
     * 设置数据源
     * @param dataSource
     */
    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    /**
     * 获取数据源
     * @return
     */
    public static String getDataSource() {
        return contextHolder.get();
    }

    /**
     * 清除数据源
     */
    public static void clearDataSource() {
        contextHolder.remove();
    }
}
DynamicDataSource.java

    3.2.4 读取数据源并配置动态数据源

package com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 王杨帅
 * @create 2018-11-18 21:42
 * @desc 动态数据源读取与配置
 **/
@Configuration
public class DynamicDataSourceConfig {
    /**
     * 读取并配置数据源1
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 读取并配置数据源2
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 根据数据源1和数据源2配置动态数据源
     * @param primaryDataSource
     * @param secondaryDataSource
     * @return
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(
            DataSource primaryDataSource,
            DataSource secondaryDataSource
    ) {
        Map<String, DataSource> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.FIRST, primaryDataSource);
        targetDataSources.put(DataSourceNames.SECOND, secondaryDataSource);
        return new DynamicDataSource(primaryDataSource, targetDataSources);
    }

}
DynamicDataSourceConfig.java

    3.2.5 创建指定数据源的注解

package com.xunyji.spring_jdbc_datasource.comm.datasource;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default "";
}
DataSource.java

    3.2.6 创建常量接口

package com.xunyji.spring_jdbc_datasource.comm.datasource;

/**
 * @author 王杨帅
 * @create 2018-12-19 22:28
 * @desc
 **/
public interface DataSourceNames {
    String FIRST = "first";
    String SECOND = "second";
}
DataSourceNames.java

    3.2.7 创建切换数据源的AOP

package com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author 王杨帅
 * @create 2018-11-18 21:53
 * @desc 数据源切换AOP
 **/
@Aspect
@Component
@Slf4j
public class DataSourceAspect implements Ordered{

    @Pointcut("@annotation(com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce.DataSource)")
    public void dataSourcePointCut() {}

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        DataSource ds = method.getAnnotation(DataSource.class);
        if(ds == null){
            DynamicDataSource.setDataSource(DataSourceNames.FIRST);
            log.debug("set datasource is " + DataSourceNames.FIRST);
        }else {
            DynamicDataSource.setDataSource(ds.name());
            log.debug("set datasource is " + ds.name());
        }

        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
            log.debug("clean datasource");
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
DataSourceAspect.java

    3.2.8 常见错误

      错区1:由于利用AOP实现的所数据源,所以需要额外导入Aspect相关的依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>

      错误2:启动应用后会出现循环应用错误,错误信息如下

      原因:SpringBoot默认对数据源进行了配置,如果想要动态数据源生效就必须关闭数据源的自动配置

      解决:在启动类的注解中排除掉数据源自动配置类即可

     3.2.9 知识点复习

      》ThreadLocal原理与应用场景

      》AbstractRoutingDataSource原理解析

      

二、整合MyBatis

1 ORM框架

(摘自:https://gitbook.cn/gitchat/column/5b86228ce15aa17d68b5b55a/topic/5be8f0552c33167c317c6a7f)

  1.1 概念

    对象关系映射(Object Relational Mapping,ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM 是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

  1.2 为什么需要ORM

    当你开发一个应用程序的时候(不使用 O/R Mapping),可能会写不少数据访问层代码,用来从数据库保存、删除、读取对象信息等;在 DAL 中写了很多的方法来读取对象数据、改变状态对象等任务,而这些代码写起来总是重复的。针对这些问题 ORM 提供了解决方案,简化了将程序中的对象持久化到关系数据库中的操作。

    ORM 框架的本质是简化编程中操作数据库的编码,在 Java 领域发展到现在基本上就剩两家最为流行,一个是宣称可以不用写一句 SQL 的 Hibernate,一个是以动态 SQL 见长的 MyBatis,两者各有特点。在企业级系统开发中可以根据需求灵活使用,会发现一个有趣的现象:传统企业大都喜欢使用 Hibernate,而互联网行业通常使用 MyBatis。

  

2 MyBatis介绍

(摘自:https://gitbook.cn/gitchat/column/5b86228ce15aa17d68b5b55a/topic/5be8f0552c33167c317c6a7f)

  2.1 概念

    MyBatis 支持普通的 SQL 查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及对结果集的检索封装。MaBatis 可以使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。

  2.2 优点

    SQL 被统一提取出来,便于统一管理和优化
    SQL 和代码解耦,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰、更易维护、更易单元测试
    提供映射标签,支持对象与数据库的 ORM 字段关系映射
    提供对象关系映射标签,支持对象关系组件维护
    灵活书写动态 SQL,支持各种条件来动态生成不同的 SQL

  2.3 缺点   

    编写 SQL 语句时工作量很大,尤其是字段多、关联表多时,更是如此
    SQL 语句依赖于数据库,导致数据库移植性差

3 mybatis原理解析

  待更新2018年12月27日10:54:46

4 SpringBoot整合Mybatis(xml版本)

  4.1 引入依赖

    引入web启动依赖和mysql、mybatis相关依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xunyji</groupId>
    <artifactId>mybatis_xml_datasource</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mybatis_xml_datasource</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
pom.xml

  4.2 规范项目路径

    4.2.1 路径说明

      mapper接口:就是一般的持久层接口而已,一般放在repository包下;mapper接口位于程序源文件目录

      mapper文件:就是书写sql语句的文件,每个mapper映射文件都和一个mapper接口一一对应;mapper映射文件和mybatis的配置文件都位于resources目录

      mybatis配置文件:就是mybatis的一些基本配置信息;mybatis配置文件一般和存放mappr映射文件的目录处于一个目录下【本案例处于resouces目录下的mybatis目录下】

      路径配置:由于mybatis官方提供的启动包提供了一个配置类来配置mapper映射文件和配置文件的路径【org.mybatis.spring.boot.autoconfigure.MybatisProperties】

      项目结构如下所示:

    4.2.2 配置类

      配置类:org.mybatis.spring.boot.autoconfigure.MybatisProperties,可配置的选项和配置实例如下所示

  4.3 数据源配置

    在application.yml文件中配置基本数据源

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: 182838
    driver-class-name: com.mysql.cj.jdbc.Driver

  4.4 mybatis配置文件

    mybatis配置文件位于resouces目录下的mybatis文件夹下,需要在application.yml中指定mybatis配置文件的位置

<?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>

  4.5 数据表和实体类

    在数据源对应的数据库中创建一张users表

/*
Navicat MySQL Data Transfer

Source Server         : mysql5.4
Source Server Version : 50540
Source Host           : localhost:3306
Source Database       : testdemo

Target Server Type    : MYSQL
Target Server Version : 50540
File Encoding         : 65001

Date: 2018-12-25 15:16:15
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `users`
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `userName` varchar(32) DEFAULT NULL COMMENT '用户名',
  `passWord` varchar(32) DEFAULT NULL COMMENT '密码',
  `user_sex` varchar(32) DEFAULT NULL,
  `nick_name` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES ('1', 'assassin', '阿斯蒂芬', 'MAN', '暗室逢灯');
INSERT INTO `users` VALUES ('28', 'aa', 'a123456', 'MAN', null);
INSERT INTO `users` VALUES ('29', 'bb', 'b123456', 'WOMAN', null);
INSERT INTO `users` VALUES ('30', 'cc', '0134123', 'WOMAN', null);
INSERT INTO `users` VALUES ('31', 'aa', 'a123456', 'MAN', null);
INSERT INTO `users` VALUES ('32', 'bb', 'b123456', 'WOMAN', null);
INSERT INTO `users` VALUES ('33', 'cc', 'b123456', 'WOMAN', null);
INSERT INTO `users` VALUES ('34', null, '123321', null, null);
INSERT INTO `users` VALUES ('35', null, '123321', 'MAN', null);
INSERT INTO `users` VALUES ('36', null, '123321', 'MAN', null);
INSERT INTO `users` VALUES ('37', '王杨帅', '1234', null, null);
INSERT INTO `users` VALUES ('38', '杨玉林', null, 'MAN', null);
users

    在项目中创建一个实体类类User和数据源中的users表对应

    技巧01:可以利用IDEA直接生成,参考文档

    注意:User实体类中用到了一个枚举

package com.xunyji.mybatis_xml.model.enums;

/**
 * @author 王杨帅
 * @create 2018-12-25 14:17
 * @desc
 **/
public enum UserSexEnum {
    WOMAN,
    MAN
}
UserSexEnum.java
package com.xunyji.mybatis_xml.model.entity;

import com.xunyji.mybatis_xml.model.enums.UserSexEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 王杨帅
 * @create 2018-12-25 14:18
 * @desc
 **/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
    private Long id;
    private String userName;
    private String passWord;
    private UserSexEnum userSex;
    private String nickName;
}
User.java

  4.6 创建mapper接口

    技巧01:跟一般的接口一样,需要在接口上标注@Repository注解

package com.xunyji.mybatis_xml.repository;

import com.xunyji.mybatis_xml.model.entity.User;
import com.xunyji.mybatis_xml.model.param.UserParam;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author 王杨帅
 * @create 2018-12-23 11:31
 * @desc
 **/
@Repository
public interface UserRepository {
    List<User> getAll();
    User getOne(Long id);
    void insert(User user);
    void update(User user);
    void delete(Long id);
    List<User> getList(UserParam userParam);
    Integer getCount(UserParam userParam);
}
UserRepository.java

  4.7 创建mapper映射文件

    技巧01:mapper映射文件位于resources目录下,而且需要在application.yml中配置mapper映射文件的位置

<?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.xunyji.mybatis_xml.repository.UserRepository" >

    <!--表结构和实体类的映射关系 start-->
    <resultMap id="BaseResultMap" type="com.xunyji.mybatis_xml.model.entity.User" >
        <id column="id" property="id" jdbcType="BIGINT" />
        <result column="userName" property="userName" jdbcType="VARCHAR" />
        <result column="passWord" property="passWord" jdbcType="VARCHAR" />
        <result column="user_sex" property="userSex" jdbcType="VARCHAR" javaType="com.xunyji.mybatis_xml.model.enums.UserSexEnum"/>
        <result column="nick_name" property="nickName" jdbcType="VARCHAR" />
    </resultMap>
    <!--表结构和实体类的映射关系 end-->

    <sql id="Base_Column_List" >
        id, userName, passWord, user_sex, nick_name
    </sql>

    <sql id="Base_Where_List">
        <if test="userName != null  and userName != ''">
            and userName = #{userName}
        </if>
        <if test="userSex != null and userSex != ''">
            and user_sex = #{userSex}
        </if>
    </sql>

    <!--查询所有数据 start-->
    <select id="getAll" resultMap="BaseResultMap"  >
        SELECT
        <include refid="Base_Column_List" />
        FROM users
    </select>
    <!--查询所有数据 end-->

    <!--分页查询 start-->
    <select id="getList" resultMap="BaseResultMap" parameterType="com.xunyji.mybatis_xml.model.param.UserParam">
        select
        <include refid="Base_Column_List" />
        from users
        where 1=1
        <include refid="Base_Where_List" />
        order by id ASC
        limit #{beginLine} , #{pageSize}
    </select>
    <!--分页查询 end-->

    <!--记录总数 start-->
    <select id="getCount" resultType="Integer" parameterType="com.xunyji.mybatis_xml.model.param.UserParam">
        select
        count(1)
        from users
        where 1 = 1
        <include refid="Base_Where_List" />
    </select>
    <!--记录总数-->

    <!--根据ID获取数据 start-->
    <select id="getOne" parameterType="Long" resultMap="BaseResultMap" >
        SELECT
        <include refid="Base_Column_List" />
        FROM users
        WHERE id = #{id}
    </select>
    <!--根据ID获取数据 end-->

    <!--插入数据 start-->
    <insert id="insert" parameterType="com.xunyji.mybatis_xml.model.entity.User" >
        INSERT INTO
        users
        (userName,passWord,user_sex, nick_name)
        VALUES
        (#{userName}, #{passWord}, #{userSex}, #{nickName})
    </insert>
    <!--插入数据 end-->

    <!--更新数据 start-->
    <update id="update" parameterType="com.xunyji.mybatis_xml.model.entity.User" >
        UPDATE
        users
        SET
        <if test="userName != null">userName = #{userName},</if>
        <if test="passWord != null">passWord = #{passWord},</if>
        nick_name = #{nickName}
        WHERE
        id = #{id}
    </update>
    <!--更新数据 end-->

    <!--删除数据 start-->
    <delete id="delete" parameterType="Long" >
        DELETE FROM
        users
        WHERE
        id =#{id}
    </delete>
    <!--删除数据 end-->

</mapper>
UserMapper.xml

  4.8 必备配置

    4.8.1 启动类配置

      需要在启动类上指定mapper接口路径

    4.8.2 application.yml配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: 182838
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml
  type-aliases-package: com.xunyji.mybatis_xml.model
application.yml

  4.9 测试类

package com.xunyji.mybatis_xml.repository;

import com.sun.org.apache.xml.internal.security.keys.keyresolver.implementations.X509IssuerSerialResolver;
import com.xunyji.mybatis_xml.model.entity.User;
import com.xunyji.mybatis_xml.model.enums.UserSexEnum;
import com.xunyji.mybatis_xml.model.param.UserParam;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

import static org.junit.Assert.*;

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void getAll() throws Exception {
        List<User> userList = userRepository.getAll();
        userList.forEach(System.out::println);
    }

    @Test
    public void getOne() throws Exception {
        List<User> userList = userRepository.getAll();
        log.info("获取到的user列表为:" + userList);
        List<Long> idList = userList.stream()
                .map(user -> user.getId())
                .collect(Collectors.toList());
        log.info("获取到的ID列表为:" + idList);

        int index = new Random().nextInt(idList.size()); // 随机获取一个idList的索引号
        Long indexValue = idList.get(index); // 获取随机索引号index在idList中的对应值

        User user = userRepository.getOne(indexValue);
        log.info("ID值为{}的用户信息为:{}", indexValue, user);

    }

    @Test
    public void insert() throws Exception {
        User user = User.builder()
                .userName("王毅凌")
                .passWord("100101")
                .userSex(UserSexEnum.MAN)
                .nickName("asdasdf")
                .build();
        log.info("封装的user对象为:" + user);

        userRepository.insert(user);

        List<User> all = userRepository.getAll();
        all.forEach(System.out::println);
    }

    @Test
    public void update() throws Exception {
        User user = User.builder()
                .id(38l)
                .userName("王毅凌")
                .passWord("010101")
                .userSex(UserSexEnum.MAN)
                .nickName("毅凌")
                .build();
        userRepository.update(user);

        List<User> all = userRepository.getAll();
        all.forEach(System.out::println);
    }

    @Test
    public void delete() throws Exception {
        log.info("删除前:");
        userRepository.getAll().forEach(System.out::println);
        userRepository.delete(41l);
        log.info("删除后:");
        userRepository.getAll().forEach(System.out::println);

    }

    @Test
    public void getList() throws Exception {
        UserParam userParam = UserParam.builder()
                .build();
        userParam.setCurrentPage(2);
        userParam.setPageSize(3);
        log.info("起始页码为:" + userParam.getBeginLine());
        List<User> list = userRepository.getList(userParam);
        list.forEach(System.out::println);
    }

    @Test
    public void getCount() throws Exception {
        UserParam userParam = UserParam.builder()
                .build();
        userParam.setCurrentPage(2);
        userParam.setPageSize(3);
        Integer count = userRepository.getCount(userParam);
        log.info("记录数为:" + count);
    }

    @Test
    public void testDemo() {

    }

}
View Code

   4.10 多数据源

    4.10.1 多数据源配置文件

spring:
  datasource:
    primary:
      jdbc-url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: root
      password: 182838
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      jdbc-url: jdbc:mysql://localhost:3306/testdemo4?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: root
      password: 182838
      driver-class-name: com.mysql.cj.jdbc.Driver

    4.10.2 数据源名称

      技巧01:利用一个接口来管理数据源名称,也可以利用一个枚举类型来实现

package com.xunyji.mybatis_xml_datasource.config.datasource;

/**
 * @author 王杨帅
 * @create 2018-12-27 15:22
 * @desc 多数据源名称
 **/
public interface DataSourceNames {
    String FIRST = "first";
    String SECOND = "second";
}
DataSourceNames.java

    4.10.3 扩展Spring的AbstractRoutingDataSource抽象类

      AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现多数据 源的核心,并对该方法进行Override。

package com.xunyji.mybatis_xml_datasource.config.datasource;

import com.sun.xml.internal.bind.v2.util.DataSourceSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

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

/**
 * @author 王杨帅
 * @create 2018-12-27 15:23
 * @desc 读取配置文件中的数据源信息
 **/
@Configuration
public class DynamicDataSourceConfig {

    @Bean
    @ConfigurationProperties(value = "spring.datasource.primary")
    public DataSource firstDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(value = "spring.datasource.secondary")
    public DataSource secondDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
        Map<String, DataSource> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
        targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }

}
DynamicDataSourceConfig.java

    4.10.4 配置DataSource,指定数据源的信息

package com.xunyji.mybatis_xml_datasource.config.datasource;

import com.sun.xml.internal.bind.v2.util.DataSourceSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

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

/**
 * @author 王杨帅
 * @create 2018-12-27 15:23
 * @desc 读取配置文件中的数据源信息
 **/
@Configuration
public class DynamicDataSourceConfig {

    /**
     * 读取第一个数据源信息
     * @return
     */
    @Bean
    @ConfigurationProperties(value = "spring.datasource.primary")
    public DataSource firstDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 读取第二个数据源信息
     * @return
     */
    @Bean
    @ConfigurationProperties(value = "spring.datasource.secondary")
    public DataSource secondDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 配置主数据源,并将数据源信息集合传入DynamicDataSource中
     * @param firstDataSource
     * @param secondDataSource
     * @return
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
        Map<String, DataSource> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
        targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }

}
DynamicDataSourceConfig.java

    4.10.5 数据源切换注解

package com.xunyji.mybatis_xml_datasource.config.datasource;

import java.lang.annotation.*;

/**
 * 数据源切换注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default "";
}
DataSource.java

    4.10.6 用于切换数据源的切面

package com.xunyji.mybatis_xml_datasource.config.datasource;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author 王杨帅
 * @create 2018-12-27 15:58
 * @desc 切面类
 **/
@Aspect
@Component
@Slf4j
public class DataSourceAspect implements Ordered {

    @Pointcut("@annotation(com.xunyji.mybatis_xml_datasource.config.datasource.DataSource)")
    public void dataSourcePointCut() {}

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        DataSource ds = method.getAnnotation(DataSource.class);
        if(ds == null){
            DynamicDataSource.setDataSource(DataSourceNames.FIRST);
            log.debug("set datasource is " + DataSourceNames.FIRST);
        }else {
            DynamicDataSource.setDataSource(ds.name());
            log.debug("set datasource is " + ds.name());
        }

        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
            log.debug("clean datasource");
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
DataSourceAspect.java

    4.10.7 启动类配置

      由于SpringBoot的自动配置原理,所以需要将SpringBoot配置的DataSource排除掉

    4.10.8 添加AOP依赖

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

    4.10.9 利用注解切换数据源

      在Repository层中的方法中利用自定义的数据源切换注解标注该方法使用的数据源

 

原文地址:https://www.cnblogs.com/NeverCtrl-C/p/9978406.html