Spring Boot与数据访问

170 阅读9分钟

文章目录


1. 概述

对于数据访问层,无论是SQL还是NOSQL,Spring Boot默认采用整合Spring Data的方式进行统一处理,添加大量自动配置,屏蔽了很多设置。引入各种xxxTemplate,xxxRepository来简化我们对数据访问层的操作。对我们来说只需要进行简单的设置即可。
在这里插入图片描述

2. JDBC

Spring 使用JDBC访问以及操作数据库需要进行如下步骤的操作:

  • 导入JDBC依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
  • 配置数据库连接信息

    spring:
      datasource:
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/sql_store?serverTimezone=GMT
        driver-class-name: com.mysql.cj.jdbc.Driver
    

和数据源相关的配置都可以在DataSourceProperties类中找到,所以通过spring.datasource.xxx可以配置相关的内容。

配置结束后,使用单元测试获取数据源以及连接:

@RunWith(SpringRunner.class)
@SpringBootTest
class DyliangApplicationTests {

    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());

        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }
}

此时控制台输出:

class com.zaxxer.hikari.HikariDataSource
HikariProxyConnection@2111669429 wrapping com.mysql.cj.jdbc.ConnectionImpl@73aeef7d

可见这里Spring Boot默认使用过的HikariDataSource,除此之外,Spring Boot还支持org.apache.tomcat.jdbc.pool.DataSource和BasicDataSource。

从DataSourceConfiguration中找到Spring Boot支持的数据源。

abstract class DataSourceConfiguration {

    @SuppressWarnings("unchecked")
    protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
        return (T) properties.initializeDataSourceBuilder().type(type).build();
    }

    /**
	 * Tomcat Pool DataSource configuration.
	 */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
    @ConditionalOnMissingBean(DataSource.class)
    @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource",
                           matchIfMissing = true)
    static class Tomcat {

        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.tomcat")
        org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
            org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(properties,
                                                                                 org.apache.tomcat.jdbc.pool.DataSource.class);
            DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
            String validationQuery = databaseDriver.getValidationQuery();
            if (validationQuery != null) {
                dataSource.setTestOnBorrow(true);
                dataSource.setValidationQuery(validationQuery);
            }
            return dataSource;
        }
    }

    /**
	 * Hikari DataSource configuration.
	 */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(HikariDataSource.class)
    @ConditionalOnMissingBean(DataSource.class)
    @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
                           matchIfMissing = true)
    static class Hikari {

        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.hikari")
        HikariDataSource dataSource(DataSourceProperties properties) {
            HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
            if (StringUtils.hasText(properties.getName())) {
                dataSource.setPoolName(properties.getName());
            }
            return dataSource;
        }
    }

    /**
	 * DBCP DataSource configuration.
	 */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class)
    @ConditionalOnMissingBean(DataSource.class)
    @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.commons.dbcp2.BasicDataSource",
                           matchIfMissing = true)
    static class Dbcp2 {

        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.dbcp2")
        org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties) {
            return createDataSource(properties, org.apache.commons.dbcp2.BasicDataSource.class);
        }
    }

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

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

另外,在和JDBC自动配置相关的包下还可以看到DataSourceInitializer,其中:

  • runSchemaScripts():运行建表语句
  • runDataScripts():运行插入数据的sql语句

默认只需要将文件命名为:

schema‐*.sql、data‐*.sql

默认规则使用schema.sql,schema‐all.sql。可以使用

schema:
 classpath:department.sql

可以使用

指定位置。

使用JDBC数据源还可以很方便的使用JdbcTempalte来进行数据库的相关操作,它和Spring中使用JdbcTempalte是一样的:

@Controller
public class HelloController {

    @Autowired
    JdbcTemplate jdbcTemplate;

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

3. 整合Druid数据源

虽然使用JDBC可以完成数据库的操作,但在实际的场景中,通常还需要考虑性能和并发性等其他的因素。因此,通常需要使用其他的数据源,例如阿里的Druid数据源。

使用Druid数据源首先需在pom.xml中引入相关的依赖:

<!--引入druid数据源-->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.8</version>
</dependency>

同时还需要引入log4j,不然运行项目时会报错。

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

同样的还可以配置数据源相关的内容,如下所示:

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/sql_store?serverTimezone=GMT
    driver-class-name: com.mysql.cj.jdbc.Driver
    # type用于指定数据源的类型
    type: com.alibaba.druid.pool.DruidDataSource  
	
	# druid数据源常用的配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    #  配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

但此时的配置并不会生效,因此,我们还需要写一个相关的配置类,使得数据源的获取通过自定义的配置类获取,而不是让系统通过反射得到。

@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druid() {
        return new DruidDataSource();
    }
}

此时运行单元测试可见数据源已发生改变:

class com.alibaba.druid.pool.DruidDataSource
com.mysql.cj.jdbc.ConnectionImpl@423c5404

通常使用Druid数据源环旭配置Druid的监控:


@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druid() {
        return new DruidDataSource();
    }


    //配置Druid的监控
    //1、配置一个管理后台的Servlet
    @Bean
    public ServletRegistrationBean statViewServlet() {
        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        Map<String, String> initParams = new HashMap<>();

        initParams.put("loginUsername", "admin");
        initParams.put("loginPassword", "123456");
        initParams.put("allow", "");//默认就是允许所有访问

        bean.setInitParameters(initParams);
        return bean;
    }


    //2、配置一个web监控的filter
    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());

        Map<String, String> initParams = new HashMap<>();
        initParams.put("exclusions", "*.js,*.css,/druid/*");

        bean.setInitParameters(initParams);

        bean.setUrlPatterns(Arrays.asList("/*"));

        return bean;
    }
}

配置结束后,通过localhost:8080/druid就可以来到监控程序界面:

在这里插入图片描述

4. 整合Mybatis

Spring Boot整合Mybatis进行数据持久化操作需要执行以下步骤:

  • 导入依赖

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

    mybatis-spring-boot-starter在导入时会自动导入Mybatis相关的依赖:

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
    </dependency>
    
  • 配置Druid数据源(可选,推荐):配置步骤如上面Druid数据源的配置

4.1 注解版

此时数据库中account表如下所示:

mysql> select * from account;
+----+----------+-------+
| id | name     | money |
+----+----------+-------+
|  1 | Forlogen |  1000 |
|  2 | Kobe     |  1000 |
|  3 | James    |  1000 |
| 12 | dylaing  | 23123 |
| 16 | Amy      |  1000 |
+----+----------+-------+
5 rows in set (0.00 sec)

使用注解的方式通过Mybatis进行数据持久化操作只需要执行以下几步:

  • 创建表对应的实体类

    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class Account {
    
        @Getter
        @Setter
        private Integer id;
    
        @Getter
        @Setter
        private String name;
    
        @Getter
        @Setter
        private Float money;
    }
    
  • 编写持久层接口:@Mapper表示当前接口为一个mapper,使用@Select、@Insert、@Update执行SQL操作

    @Mapper
    public interface IAccountDao {
    
        @Select("select * from account")
        public List<Account> findAll();
    
        @Select("select * from account where id=#{id}")
        public Account findById(Integer id);
    
        @Insert("insert into account(name,money) values (#{name},#{money})")
        public int insertAccount(Account account);
    
        @Update("update account set name=#{name} where id=#{id}")
        public int updateAccount(Account account);
    
    }
    
  • 编写controller:类内配置IAccountDao对象,并使用@Autowired让Spring Boot自动注入

    @RestController
    public class AccountController {
    
        @Autowired
        private IAccountDao accountDao;
    
        // http://localhost:8080/account
        @GetMapping("/account")
        public List<Account> testFindAll(){
            return accountDao.findAll();
        }
    
        // http://localhost:8080/account/1
        public Account testFindById(@PathVariable("id") Integer id){
            return accountDao.findById(id);
        }
    
        // http://localhost:8080/accountInsert?name=Amy&money=1000
        @GetMapping("/accountInsert")
        public Account testFindById(Account account){
            accountDao.insertAccount(account);
            return account;
        }
    }
    
  • 运行主程序,并在浏览器输入上面的URL便可得到表中的信息

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

如果想要添加自定义的配置,还可以使用@org.springframework.context.annotation.Configuration标识配置类,添加相应的配置:

@org.springframework.context.annotation.Configuration
public class MybatisConfig {

    public ConfigurationCustomizer configurationCustomizer(){
        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                configuration.setMapUnderscoreToCamelCase(true);
            }
        };
    }
}

4.2 XML版

使用XML配置文件的方式执行数据持久化操作,首先需要编写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>
	<!--使用驼峰命名法,也可以配置其他的项-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

表对应的实体类和注解版的一致,但对应的接口不再使用注解的方式执行SQL操作:

@Mapper
public interface IAccountDao {

    public List<Account> findAll();

    public Account findById(Integer id);

    public int insertAccount(Account account);

}

而是编写接口对应的配置文件:

<?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="dyliang.dao.IAccountDao">

    <select id="findAll" resultType="dyliang.domain.Account">
       select * from account;
    </select>

    <select id="findById" resultType="dyliang.domain.Account">
        select * from account where id=#{id};
    </select>

    <insert id="insertAccount">
        insert into account(name,money) values (#{name},#{money}
    </insert>
</mapper>

编写的配置文件需要让Spring Boot知道在哪,这样在程序加载时才能使用。因此,还需要在Spring Boot的配置文件application.yml或是application.properties中配置Mybatis配置文件的位置:

mybatis:
  # 指定全局配置文件位置
  config-location: classpath:mybatis/SqlMapConfig.xml
  # 指定sql映射文件位置
  mapper-locations: classpath:mybatis/dao/*.xml

同样需要编写一个Controller,内容和上面的相同:

@RestController
public class AccountController {

    @Autowired
    private IAccountDao accountDao;

    // http://localhost:8080/account
    @GetMapping("/account")
    public List<Account> testFindAll(){
        return accountDao.findAll();
    }

    // http://localhost:8080/account/1
    @GetMapping("account/{id}")
    public Account testFindById(@PathVariable("id") Integer id){
        return accountDao.findById(id);
    }

    // http://localhost:8080/accountInsert?name=Amy&money=1000
    @GetMapping("/accountInsert")
    public Account testFindById(Account account){
        accountDao.insertAccount(account);
        return account;
    }
}

执行主程序就可以在浏览器中得到相关的结果。

5. 整合Spring Data Jpa

在这里插入图片描述

5.1 Spring Data

Spring Data 项目的目的是为了简化构建基于Spring 框架应用的数据访问技术,包括非关系数据库、Map-Reduce 框架、云数据服务等等;另外也包含对关系数据库的访问支持。

Spring Data 包含多个子项目:

  • Spring Data Commons
  • Spring Data JPA
  • Spring Data KeyValue
  • Spring Data LDAP
  • Spring Data MongoDB
  • Spring Data Gemfire
  • Spring Data REST
  • Spring Data Redis
  • Spring Data for Apache Cassandra
  • Spring Data for Apache Solr
  • Spring Data Couchbase (community module)
  • Spring Data Elasticsearch (community module)
  • Spring Data Neo4j (community module)

Spring Data提供了统一的API来对数据访问层进行操作,只要基于Spring Data Commons项目。Spring Data Commons让我们在使用关系型或者非关系型数据访问技术时都基于Spring提供的统一标准,标准包含了CRUD(创建、获取、更新、删除)、查询、排序和分页的相关操作。

Spring Data提供了统一的Repository接口,我们不需要自己编写相关的Dao接口,只需要继承提供的Repository接口,就可以使用其中有关SQL的操作来操作数据库。相关的接口有:

  • Repository<T, ID extends Serializable>:统一接口
  • RevisionRepository<T, ID extends Serializable, N extends Number & Comparable<N>>:基于乐观锁机制
  • CrudRepository<T, ID extends Serializable>:基本CRUD操作
  • PagingAndSortingRepository<T, ID extends Serializable>:基本CRUD及分页

在这里插入图片描述

同时它还提供了数据访问的模板类xxxTemplate,如MongoTemplate、RedisTemplate等。

5.2 案例

同样使用account表来看一下如何使用Jpa进行数据持久化操作,首先,依然是依然相关的依赖:

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

然后编写实体类。并配置好映射关系:

@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "account")
public class Account {

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

    @Getter
    @Setter
    @Column
    private String name;

    @Getter
    @Setter
    @Column
    private Float money;
}

其中:

  • @Entity:必选,表示该类对应数据库中的一张表
  • @Table(name = “AUTH_USER”) :可选,声明了数据库实体对应的表信息。包括表名称、索引信息等。这里声明这个实体类对应的表名是 account。如果没有指定,则表名和实体的名称保持一致
  • @Id:声明主键
  • @Column:声明实体属性的表字段的定义,默认的实体每个属性都对应了表的一个字段,字段的名称默认和属性名称保持一致(并不一定相等),字段的类型根据实体属性类型自动推断

编写Dao接口操作数据表,该接口要继承Repository接口中的一个:

public interface AccountRepository extends JpaRepository<Account, Integer> {
}

其中JpaRepository的泛型的第一个元素为对应的实体类,第二个为主键的数据类型。

JpaRepository源码如下,我们可以使用它提供的方法来执行SQL操作:

@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort var1);

    List<T> findAllById(Iterable<ID> var1);

    <S extends T> List<S> saveAll(Iterable<S> var1);

    void flush();

    <S extends T> S saveAndFlush(S var1);

    void deleteInBatch(Iterable<T> var1);

    void deleteAllInBatch();

    T getOne(ID var1);

    <S extends T> List<S> findAll(Example<S> var1);

    <S extends T> List<S> findAll(Example<S> var1, Sort var2);
}

在Spring Boot的配置文件中添加Jpa相关的配置:

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/sql_store?serverTimezone=GMT
    driver-class-name: com.mysql.cj.jdbc.Driver

    jpa:
      hibernate:
        # 更新或者创建数据表结构
        ddl-auto: update
      # 控制台显示SQL
      show-sql: true

最后编写controller,直接使用JpaRepository接口中的方法来执行SQL操作:

@RestController
public class AccountController {

    @Autowired
    AccountRepository accountRepository;

    @GetMapping("/account/{id}")
    public Optional<Account> findOne(@PathVariable("id") Integer id){
        Optional<Account> one = accountRepository.findById(id);
        return one;
    }

    @GetMapping("/account")
    public List<Account> findAll(){
        List<Account> all = accountRepository.findAll();
        return all;
    }


    @GetMapping("/accountInsert")
    public Account insertAccount(Account account){
        Account save = accountRepository.save(account);
        return save;
    }
}

执行主程序在浏览器中获取相关的结果。

6. 参考

SpringBoot 中 JPA 的使用

Accessing Data with JPA