大数据、Java EE 学习资料请关注 B 站:https://space.bilibili.com/204792350

SpringBoot 数据访问层的操作,更换 Druid 数据源,对于 ORM 的整合(Mybatis,JDBC,JPA),

简介

SpringBoot 对于数据访问层,无论是 SQL 还是 NoSQL,SpringBoot 默认采用整合 SpringData 的方式进行统一处理,添加大量的自动配置。

引入 xxxTemplate、xxxRepository 来讲话对数据访问层的操作,只需要简单的设置即可。

SpringData 主要模块

社区模块

整合 ORM 与数据源

JDBC

引入 starter 依赖:spring-boot-starter-jdbc

配置基本设置:DataSourceProperties,关于 DataSource 的配置项都在这个属性类中。

spring:
    datasource:
        username: root
        password: 123123
        url: jdbc:mysql://192.168.174.100:3306/jdbc
        driver-class-name: com.mysql.jdbc.Driver

通过 JdbcTemplateConfiguration 配置类中,有配置了一个模板类,通过这个类可以操作数据库。

@Controller
public class HelloController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @ResponseBody
    @GetMapping("/query")
    public List<Map<String, Object>> query(Model map) {
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from student");
        return maps;
    }
}

Mybatis

SpringBoot 中,只需要导入 starter 即可。

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.2</version>
</dependency>

这样,项目中就可以使用 mybatis ORM,由于 Mybatis 依赖有自动配置类,所以不需要配置也可以使用。

注解版

相关注解:

  • @Mpper
  • @MpperScan
    • 将这个注解标注在 SpringBoot 主程序类上面,定义扫描基础包,在该包下面都是 Mapper 接口。然后,每一个 Mapper 接口就不用标注 @Mapper 注解了。
    • 与这个类似的 @MapperScans,作用扫描多个包。
  • @Select
  • @Insert
  • @Update
  • @Delete
  • @Option

这一些注解是常用注解,通过名称就可以知道是什么作用。

编写一个 mapper 接口:

@Mapper
public interface DepartmentMapper {

    @Select("select * from department where id=#{id}")
    Department getDepartment(Integer id);

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert("insert into department(departmentName) value(#{departmentName})")
    Integer saveDepartment(Department department);

}

需要注意一点的是,在用这个注解:@Options(useGeneratedKeys = true, keyProperty = "id"),表示获取操作数据之后,返回自增主键值。

后面需要到返回数据的时候,就会将对象中 key 更新。

获取数据:

public Department show(@PathVariable("id") Integer id) {
    return departmentMapper.getDepartment(id);
}

public Department show(Department department) {
    departmentMapper.saveDepartment(department);
    return department;
}

自动转换驼峰命名

有两种方式:

1)在配置文件中定义

mybatis:
    configuration:
        map-underscore-to-camel-case: true

2)创建一个定制器

@SpringBootConfiguration
public class MybatisConfiguration {

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setMapUnderscoreToCamelCase(true);
    }
}

配置版

需要步骤:

  • 创建全局配置文件
  • 在 SpringBoot 中指定全局配置文件,以及 Mapper 映射文件的路径位置
  • 创建 Mapper 映射接口,以及 SQL 映射文件
  • 在 SQL 映射文件中编写 sql 语句

mybatis 的配置文件及映射文件结构类似这样:

Snipaste_2020-05-28_22-09-36

全局配置文件结构大概如此:

<?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>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

所有内容都在 configuration 标签内,这里用了一个驼峰命名转换设置。

在 SpringBoot 配置文件中,配置 Mybatis 相关配置:

mybatis:
	// mybatis 全局配置文件位置
    config-location: classpath:/mybatis/mybatis-config.xml
    // mapper 接口映射文件配置
    mapper-locations: classpath:/mybatis/mapper/*.xml

SQL 语句映射文件结构:

<?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.huasio.jdbc.mpper.EmployeeMapper">

    <select id="getEmployee" resultType="com.huasio.jdbc.bean.Employee">
        select * from employee where id=#{id}
    </select>
    
</mapper>

所有 sql 语句以及其他内容都在 mapper 标签内编写。

需要特别注意的地方,就是 namespace 这个属性一定要对应相应的 Mapper 接口,否则无法匹配。

这里的 select 标签,类比 @Select 注解,同样在里面编写语句,这里其他配置通过 select 的属性来设置,id 表示方法名,resultType 表示返回数据的类型。

Mapper 接口:

@Mapper
public interface EmployeeMapper {

    Employee getEmployee(Integer id);

}

参数列表可以在 sql 映射文件中通过 #{参数} 这样的方式来获取,对象内属性可以通过级联属性的方式获取。

这样的结构就可以正常通过 mybatis 持久化层来访问 MySQL 的数据库。

注意点

使用配置版的方式,需要在配置文件中配置 mybatis 的全局配置文件加载路径,所以在 springboot 配置文件中不能出现 configuration 配置项,这个会与 Mybatis 全局配置文件冲突。

错误配置:

mybatis:
    configuration:
        map-underscore-to-camel-case: true
    config-location: classpath:/mybatis/mybatis-config.xml
    mapper-locations: classpath:/mybatis/mapper/*.xml

configuration 与 mapper-locations 同时配置,抛出异常:

Factory method 'sqlSessionFactory' threw exception; nested exception is java.lang.IllegalStateException: Property 'configuration' and 'configLocation' can not specified ......

解决办法,将 configuration 删除,关于 mybatis 的设置,都在 mybatis 全局配置文件中设置。

整合 JPA

需要步骤:

  • 引入依赖
  • 编写实体类,跟普通 JavaBean 不同,一个实体类对应一张表
  • 编写 Repositry,主要是接口方法,类似 Mapper 的接口
  • 配置 JPA

引入依赖:

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

编写实体类:

package com.huasio.jpa.entity;

import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;

/**
 * @author yu
 */
@Entity
@Table(name = "t_user")
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "last_name", length = 255)
    private String lastName;

    @Column
    private String email;

	// ... setter and getter
}
  • @Entity:表示这个了是一个实体类,可以被托管的类
  • @Table:表示制定一些表特性,比如表名,每一个实体类都是一张表,运行的时候会自动更新,实体类发生改变,表也会发生改变。
  • @Id:表示主键
  • @GeneratedValue:给一个属性标注一些特定属性,定义主键生成策略的类型。
  • @Column:表示一列。

编写 Repository 接口:

public interface UserRepository extends JpaRepository<UserEntity, Integer> {
}

继承 JpaRepository 接口,有两个泛型,第一个是实体类,第二个是主键类型。

测试 Jpa:

@Controller
public class UserController {

    @Autowired
    UserRepository userRepository;

    @ResponseBody
    @GetMapping("/show/{id}")
    public UserEntity show(@PathVariable("id") Integer id) {
        UserEntity userEntity = new UserEntity();
        userEntity.setLastName("test");
        userEntity.setEmail("test@qq.com");
        return userRepository.save(userEntity);
    }
}

调用了 userRepository 这个类之后,首先会自动创建表,其次才是其余操作。

更换数据源

更换 Druid 数据源,在 SpringBoot 2.x 中,有两种方式:

  • 一种还是和以前的方式一样,导入 druid 依赖
  • 新方式是导入 druid 的 starter

Druid Starter 方式

1)导入 pom 依赖项

其实,SpringBoot 项目中有这个 starter,只需要导入druid starter 就行,不需要指定版本,

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.17</version>
</dependency>

2)添加配置

spring.datasource.url= 
spring.datasource.username=
spring.datasource.password=
# ...其他配置(可选,不是必须的,使用内嵌数据库的话上述三项也可省略不填)

就这么简单,不需要另作其他配置。

若是需要开启监控,则通过在配置文件中开启即可,不需要另外编写配置类。

spring:
    datasource:
        username: root
        password: 123123
        url: jdbc:mysql://192.168.174.100:3306/jdbc
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
            web-stat-filter:
                enabled: true
            stat-view-servlet:
                reset-enable: true
                login-username: root
                login-password: 123123

解析:

  • web-stat-filter:表示是否开启监控,开启之后可以访问:http://localhost/druid 监控页面
  • stat-view-servlet:配置监控 servlet 相关,可以配置登录账号密码,不重置数据等等

这些 filter 都是 Druid 内置的,所有配置都可以在配置文件中设置。

自动配置原理

默认数据源

默认的数据源:com.mysql.cj.jdbc.ConnectionImpl

SpringBoot 还支持:

com.mysql.cj.jdbc.ConnectionImpl
org.apache.tomcat.jdbc.pool.DataSource.class
org.apache.commons.dbcp2.BasicDataSource.class

除此之外,还可以自定数据源,自定义数据源需要在配置文件中配置一个属性,SpringBoot 根据这个配置来判断类型,这个属性是必须实现 DataSource 接口的类型。

spring.datasource.type
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {

   @Bean
   DataSource dataSource(DataSourceProperties properties) {
      return properties.initializeDataSourceBuilder().build();
   }

}

通过 DataSourceProperties 来返回一个构建,通过这个构建来构建自定义的数据源。

public DataSourceBuilder<?> initializeDataSourceBuilder() {
   return DataSourceBuilder.create(getClassLoader()).type(getType()).driverClassName(determineDriverClassName())
         .url(determineUrl()).username(determineUsername()).password(determinePassword());
}

这些属性都可以在配置文件中配置,前缀是:spring.datasource。

调用 build, 就是返回自定义的数据源。

public T build() {
   Class<? extends DataSource> type = getType();
   DataSource result = BeanUtils.instantiateClass(type);
   maybeGetDriverClassName();
   bind(result);
   return (T) result;
}

默认执行 SQL 文件

在 SpringBoot 启动的时候会初始化数据源,根据默认配置,会初始化嵌入式数据源,则数据源初始化的时候会加载执行以下两种语句:

  • 建表语句
  • 数据查询语句

初始化建表文件

通过查看 DataSourceInitializer 类,可以发现,初始化的默认规则。

boolean createSchema() {
   List<Resource> scripts = getScripts("spring.datasource.schema", this.properties.getSchema(), "schema");
   if (!scripts.isEmpty()) {
      if (!isEnabled()) {
         logger.debug("Initialization disabled (not running DDL scripts)");
         return false;
      }
      String username = this.properties.getSchemaUsername();
      String password = this.properties.getSchemaPassword();
      runScripts(scripts, username, password);
   }
   return !scripts.isEmpty();
}
  • getScripts:在这个方法中,会根据配置文件中的:spring.datasource.schema 属性来获取指定位置建表语句的 sql 文件,若是没有配置这个属性,则按照默认规则,从 classpath*:schema-all.sql、classpath*:schema.sql 两个文件。platform 可以通过在配置文件中自定义:spring.datasource.platform
private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) {
    if (resources != null) {
        return getResources(propertyName, resources, true);
    }
    String platform = this.properties.getPlatform();
    List<String> fallbackResources = new ArrayList<>();
    fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
    fallbackResources.add("classpath*:" + fallback + ".sql");
    return getResources(propertyName, fallbackResources, false);
}
  • 获取到建表语句文件之后,若是有需要的执行的脚本文件,那么紧接着根据初始化模式来判断初始规则。
    • 始终初始化数据源
    • 仅初始化嵌入式数据源
      • 在确保数据源是嵌入式的,则会初始化
    • 不要初始化数据源
  • 在满足脚本运行条件之后,调用 runScripts 方法,将建表文件,用户名,密码等作为参数,执行脚本。
private void runScripts(List<Resource> resources, String username, String password) {
   if (resources.isEmpty()) {
      return;
   }
   ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
   populator.setContinueOnError(this.properties.isContinueOnError());
   populator.setSeparator(this.properties.getSeparator());
   if (this.properties.getSqlScriptEncoding() != null) {
      populator.setSqlScriptEncoding(this.properties.getSqlScriptEncoding().name());
   }
   for (Resource resource : resources) {
      populator.addScript(resource);
   }
   DataSource dataSource = this.dataSource;
   if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
      dataSource = DataSourceBuilder.create(this.properties.getClassLoader())
            .driverClassName(this.properties.determineDriverClassName()).url(this.properties.determineUrl())
            .username(username).password(password).build();
   }
   DatabasePopulatorUtils.execute(populator, dataSource);
}

初始化查询文件

在初始化建表文件之后,紧接着也会跟着初始化查询操作的文件。

相关逻辑基本一致,略微的不同的是,默认加载文件名等

默认加载规则:

  • 优先从配置文件中:spring.datasource.data 获取需要加载的查询文件
  • 按照默认规则,寻找需要初始化的查询文件。
    • classpath*:data-all.sql
    • classpath*:data.sql
void initSchema() {
   List<Resource> scripts = getScripts("spring.datasource.data", this.properties.getData(), "data");
   if (!scripts.isEmpty()) {
      if (!isEnabled()) {
         logger.debug("Initialization disabled (not running data scripts)");
         return;
      }
      String username = this.properties.getDataUsername();
      String password = this.properties.getDataPassword();
      runScripts(scripts, username, password);
   }
}

注意

每一次 SpringBoot 项目启动的时候,都会初始化这些文件。在初始化一遍之后,可以通过在配置文件中 initialization-mode 属性来禁止初始化。

Snipaste_2020-05-28_16-26-14

扩展

文档

Druid 配置项

Druid Spring Boot Starter 配置属性的名称完全遵照 Druid,你可以通过 Spring Boot 配置文件来配置Druid数据库连接池和监控,如果没有配置则使用默认值。

JDBC 配置

spring.datasource.druid.url= # 或spring.datasource.url= 
spring.datasource.druid.username= # 或spring.datasource.username=
spring.datasource.druid.password= # 或spring.datasource.password=
spring.datasource.druid.driver-class-name= #或 spring.datasource.driver-class-name=

连接池配置

spring.datasource.druid.initial-size=
spring.datasource.druid.max-active=
spring.datasource.druid.min-idle=
spring.datasource.druid.max-wait=
spring.datasource.druid.pool-prepared-statements=
spring.datasource.druid.max-pool-prepared-statement-per-connection-size= 
spring.datasource.druid.max-open-prepared-statements= #和上面的等价
spring.datasource.druid.validation-query=
spring.datasource.druid.validation-query-timeout=
spring.datasource.druid.test-on-borrow=
spring.datasource.druid.test-on-return=
spring.datasource.druid.test-while-idle=
spring.datasource.druid.time-between-eviction-runs-millis=
spring.datasource.druid.min-evictable-idle-time-millis=
spring.datasource.druid.max-evictable-idle-time-millis=
spring.datasource.druid.filters= #配置多个英文逗号分隔
....//more

监控配置

# WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
spring.datasource.druid.web-stat-filter.enabled= #是否启用StatFilter默认值false
spring.datasource.druid.web-stat-filter.url-pattern=
spring.datasource.druid.web-stat-filter.exclusions=
spring.datasource.druid.web-stat-filter.session-stat-enable=
spring.datasource.druid.web-stat-filter.session-stat-max-count=
spring.datasource.druid.web-stat-filter.principal-session-name=
spring.datasource.druid.web-stat-filter.principal-cookie-name=
spring.datasource.druid.web-stat-filter.profile-enable=

# StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
spring.datasource.druid.stat-view-servlet.enabled= #是否启用StatViewServlet(监控页面)默认值为false(考虑到安全问题默认并未启动,如需启用建议设置密码或白名单以保障安全)
spring.datasource.druid.stat-view-servlet.url-pattern=
spring.datasource.druid.stat-view-servlet.reset-enable=
spring.datasource.druid.stat-view-servlet.login-username=
spring.datasource.druid.stat-view-servlet.login-password=
spring.datasource.druid.stat-view-servlet.allow=
spring.datasource.druid.stat-view-servlet.deny=

# Spring监控配置,说明请参考Druid Github Wiki,配置_Druid和Spring关联监控配置
spring.datasource.druid.aop-patterns= # Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔

Druid Spring Boot Starter 不仅限于对以上配置属性提供支持,DruidDataSource 内提供setter方法的可配置属性都将被支持。你可以参考WIKI文档或通过IDE输入提示来进行配置。配置文件的格式你可以选择.properties.yml,效果是一样的,在配置较多的情况下推荐使用.yml

# Java   SpringBoot  

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×