# spring boot数据访问

# 导入依赖

因为是一个数据相关的,所以我们就需要导入一个与数据访问相关的starts

spring-boot-starter-data-jdbc
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
1
2
3
4
5

# 分析starter

此starter会导入

我们除了导入场景以外,还需要导入驱动依赖,因为spring也不知道我们要操作哪种数据库

# 自动配置类分析

因为导入的是一个spring-boot-starter-data-jdbc的starter,所以我们就需要去自动配置依赖中,找jdbc的自动配置类

因为导入一个starter后,spring启动的时候,就会自动导入与此starter相关的配置

# 1.DataSourceAutoConfiguration

此配置是哟和数据源相关的

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
		DataSourceInitializationConfiguration.InitializationSpecificCredentialsDataSourceInitializationConfiguration.class,
		DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {}
1
2
3
4
5
6
7
8

@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")是响应式编程

可以看出,其和配置文件绑定,所以修改DataSource可以在配置文件中,进行修改@ConfigurationProperties(prefix = "spring.datasource")

从DataSourcePoolMetadataProvidersConfiguration配置类中,可以看出,如果导入这个starter,那么默认使用的数据源是HikariDataSource,因为其他的数据源都爆红,没有导入

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
static class HikariPoolDataSourceMetadataProviderConfiguration {

    @Bean
    DataSourcePoolMetadataProvider hikariPoolDataSourceMetadataProvider() {
        return (dataSource) -> {
            HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class,
                                                                           HikariDataSource.class);
            if (hikariDataSource != null) {
                return new HikariDataSourcePoolMetadata(hikariDataSource);
            }
            return null;
        };
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 2.DataSourceTransactionManagerAutoConfiguration

此配置是和事务相关的

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ JdbcTemplate.class, TransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {}
1
2
3
4
5

此配置也是和@ConfigurationProperties(prefix = "spring.datasource")绑定

此配置类,会创建一个DataSourceTransactionManager的bean

# 3.JdbcTemplateAutoConfiguration

@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class,
		NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {

}
1
2
3
4
5
6
7
8
9

和配置文件@ConfigurationProperties(prefix = "spring.jdbc")绑定

@Bean
@Primary
JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    JdbcProperties.Template template = properties.getTemplate();
    jdbcTemplate.setFetchSize(template.getFetchSize());
    jdbcTemplate.setMaxRows(template.getMaxRows());
    if (template.getQueryTimeout() != null) {
        jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
    }
    return jdbcTemplate;
}

从这里可以看出,JdbcTemplateAutoConfiguration在容器中,加入一个JdbcTemplate,所以我们可以使用自动注入的方式,并且数据源使用容器中的数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 4.XADataSourceAutoConfiguration

这是一个分布式事务配置

@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(DataSourceProperties.class)
@ConditionalOnClass({ DataSource.class, TransactionManager.class, EmbeddedDatabaseType.class })
@ConditionalOnBean(XADataSourceWrapper.class)
@ConditionalOnMissingBean(DataSource.class)
public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {}
1
2
3
4
5
6
7

因为我们不是一个分布式项目,所以没有这里的javax.transaction.TransactionManager,所以这个不会被导入

# 修改配置项

我们导入starter后,还需要配置数据源,不然启动会出现错误,会提示我们没有配置

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
	If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
1
2
3
4
5
6
7
8
9
10
11
12
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springdb
    username: root
    password: 123456
1
2
3
4
5
6

启动之后,我们就可以看到当前使用的数据源

# 整合druid数据源

@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;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

因为默认使用的数据源是Hikari,但是使用有一个条件,@ConditionalOnMissingBean(DataSource.class),所以我们可以直接在容器中,加入我们自己的DataSource,这样就可以替代原来的数据源了,从而使用druid的数据源

@Configuration
public class DataSourceConf {

    @Bean
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl();
        dataSource.setPassword();
        dataSource.setUsername();
    }
}

但是我们可以将此配置和配置文件的spring.datasource前缀进行绑定,因为配置文件修改DataSource都和DruidDataSource对应有set方法
1
2
3
4
5
6
7
8
9
10
11
12
13

那么现在我们就已经成功替换到druid的数据源了

# 配置statviewservlet

druid提供了一个用于监控的页面,我们可以配置之后,进行使用

详细配置看druid官方https://github.com/alibaba/druid

@Configuration
public class DataSourceConf {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        try {
            dataSource.setFilters("stat");
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return dataSource;
    }

    @Bean
    public ServletRegistrationBean statViewServlet() {
        StatViewServlet viewServlet = new StatViewServlet();

        ServletRegistrationBean bean = new ServletRegistrationBean(viewServlet,"/druid/*");
        bean.addInitParameter("loginUsername","chuchen");
        bean.addInitParameter("loginPassword","123456");
        return bean;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 使用druid的starter

依赖

<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.6</version>
</dependency>
1
2
3
4
5
6

# 依赖分析

因为这是一个starter,所以我们可以直接分析这个依赖的自动配置DruidDataSourceAutoConfigure

@Configuration
@ConditionalOnClass(DruidDataSource.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class,
    DruidStatViewServletConfiguration.class,
    DruidWebStatFilterConfiguration.class,
    DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {}
1
2
3
4
5
6
7
8
9

需要在此@AutoConfigureBefore(DataSourceAutoConfiguration.class)加载之前,就加载此自动配置项,因为如何在DataSourceAutoConfiguration之后才加载的话,那么就会使用默认的数据源,不会使用druid的数据源

  1. DruidSpringAopConfiguration.class

    监控springbean的配置项

  2. DruidStatViewServletConfiguration.class

    和StatView监控页面相关

    spring.datasource.druid.stat-view-servlet.enabled = true 开启监控页面
    
    1
    druid:
        stat-view-servlet:
        enabled: true #开启监控页面
        login-password: admin #登录密码
        login-username: admin #登录用户名
        reset-enable: true #启动重置按钮,true表示重置
    
    1
    2
    3
    4
    5
    6
  3. DruidWebStatFilterConfiguration.class

    web监控页面

    @ConditionalOnWebApplication
    @ConditionalOnProperty(name = "spring.datasource.druid.web-stat-filter.enabled", havingValue = "true")
    public class DruidWebStatFilterConfiguration {}
    
    1
    2
    3
  4. DruidFilterConfiguration.class

    所有Druid自己filter的配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_account
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

    druid:
      aop-patterns: com.atguigu.admin.*  #监控SpringBean
      filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)

      stat-view-servlet:   # 配置监控页功能
        enabled: true
        login-username: admin
        login-password: admin
        resetEnable: false

      web-stat-filter:  # 监控web
        enabled: true
        urlPattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'


      filter:
        stat:    # 对上面filters里面的stat的详细配置
          slow-sql-millis: 1000
          logSlowSql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

配置项列表https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8 (opens new window)

# 整合mybatis

# 引入starter

https://github.com/mybatis/spring-boot-starter

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>
1
2
3
4
5

# 依赖分析

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {}
1
2
3
4
5
6

使用mybatis时,我们一般都会使用SqlSessionFactory,SqlSession

  • 全局配置文件

  • SqlSessionFactory: 自动配置好了

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {}
    
    1
    2
    3
  • SqlSession:自动配置了 SqlSessionTemplate 组合了SqlSession

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {}
    
    1
    2
    3
  • @Import(AutoConfiguredMapperScannerRegistrar.class);

  • Mapper: 只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来

# 实例

我们引入mybatis的starter后,我们还需要写dao层,service层,还需要写一个mapper的映射文件

  • dao
@Mapper
public interface StudentMapper {
    public Student getStudentById(int id);
}

service
@Service
public class StudentSer {
    @Autowired
    StudentMapper mapper;

    public Student getStudentById(int id) {
        return mapper.getStudentById(id);
    }
}

controller:
@RestController
public class StudentController {

    @Autowired
    StudentSer studentSer;

    @GetMapping("/student")
    public Student getStudent(@RequestParam("id") int id) {
        Student studentById = studentSer.getStudentById(id);
        return studentById;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  • 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="vin.cco.qsyyke.mapper.StudentMapper">
        <select id="getStudentById" resultType="vin.cco.qsyyke.entity.Student" >
            select * from student where id = #{id}
        </select>
    </mapper>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

我们可以只写mapper的映射文件,mybatis的主配置文件,可以不用写,因为mybatis的配置都已经配置好了,数据源,sqlsessionfactory,sqlsession等等,只需要映射文件就行

但是需要在配置文件中,执行映射文件的路径

  • 修改映射文件路径

    mybatis:
      mapper-locations: classpath:mybatis/mapper/StudentMapper.xml
    
    1
    2

# 修改驼峰命名

这个是因为,我们没有开启下面这项功能

mybatis:
  mapper-locations: classpath:mybatis/mapper/StudentMapper.xml
  configuration:
    map-underscore-to-camel-case: true
1
2
3
4

# 注解方式

我们也可以不用xml文件,可以直接使用注解的方式写sql

@Insert("insert into student (name_id,age) values(#{nameId},#{age})")
public void insert(Student student);
1
2

因为表中的字段id是一个自增,我们不用写,但是这样的话,返回给我们的id字段就是0,可以使用一个注解解决

@Insert("insert into student (name_id,age) values(#{nameId},#{age})")
@Options(useGeneratedKeys = true,keyProperty = "id")
public void insert(Student student);

@PostMapping("/student")
public Student insert(Student student) {
    studentSer.insert(student);
    return student;
}
1
2
3
4
5
6
7
8
9

那么插入成功,返回的时候,就会将插入这条数据之后,id的值,赋值给student对象中的id字段

# 注解,xml混合方式

注解,xml混合方法就是,在一个mapper接口类中,其中的部分sql执行方法我们可以使用注解的方法,另外的方法可以使用xml的方式(需要在配置文件中,指明mapper.xml文件的位置),但是推荐一直使用xml方式,这样便于维护

# 技巧

因为我们每一个dao层中的接口类,都需要写一个@Mapper注解,我们可以直接在主类上,写一个注解,@MapperScan("vin.cco.qsyyke"),此注解值就是所有dao接口所在的包

# 整合mybatis-plus

# 依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>
1
2
3
4
5

# 分析依赖

后面那些灰色是,省略的,也就是和我们pom文件相冲突的包

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {}
1
2
3
4
5
6
  • SqlSessionFactory

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource){}
    
    1
    2
    3
  • SqlSession

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {}
    
    1
    2
    3

自动配置其实和mybatis一样

  • 配置文件

    private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"};
    
    1

    Locations of MyBatis mapper files.

    也就是默认的mapper的映射文件是在,静态路径下的mapper下的所有文件夹中的xml问价都是mapper的映射路径

# 实例使用

官方文档快速开始 | MyBatis-Plus (baomidou.com) (opens new window)

public interface UserMapper extends BaseMapper<User> {

}
1
2
3

BaseMapper<T>接口中,定义许多基础的增删改查操作,我们可以不用自己写

因为我们没有写过sql,实例都是使用baseMapper,其规定,使用的数据表的表名就是和实体类名一样,但是如果后期对数据表更新更改表名,需要使用@TableName("user")该注解,指明该实体类对应的表名

# 报错

org.springframework.jdbc.BadSqlGrammarException: 
### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Unknown column 'username' in 'field list'
1
2

出现这个原因是因为,这两个字段在数据库中不存在造成的

使用mybatis-plus必须要保证实体类中的字段在对应数据表中,存在,但是可以使用解决

@TableField(exist = false)
private String username;

@TableField(exist = false)
private String password;

private Long id;
private String name;
private Integer age;
private String email;
1
2
3
4
5
6
7
8
9
10

@TableField(exist = false)加上此注解,就表明该字段在对应表中,不存在,就可以解决这个问题

解决之后,成功输出结果

# 基本crud

基本的使用,查看官网CRUD 接口 | MyBatis-Plus (baomidou.com) (opens new window)

# 整合Redis

# 引入starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1
2
3
4

# 分析依赖

如何使官方整合的starter,那么他的配置包,都是在下

# RedisAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {}
1
2
3
4
5

LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class是两个连接工厂,但是通过配置文件可以发现,默认是使用的是Lettuce

需要配置

@ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", matchIfMissing = true)

  • LettuceConnectionConfiguration添加的bean
    1. DefaultClientResources
    2. LettuceConnectionFactory

# RedisRepositoriesAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EnableRedisRepositories.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnProperty(prefix = "spring.data.redis.repositories", name = "enabled", havingValue = "true",
		matchIfMissing = true)
@ConditionalOnMissingBean(RedisRepositoryFactoryBean.class)
@Import(RedisRepositoriesRegistrar.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisRepositoriesAutoConfiguration {

}
1
2
3
4
5
6
7
8
9
10
11

启动此配置需要配置spring.data.redis.repositories.enabled=true

# RedisReactiveAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ReactiveRedisConnectionFactory.class, ReactiveRedisTemplate.class, Flux.class })
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisReactiveAutoConfiguration {}
1
2
3
4

在数据访问中,以xxTemplate形式的对象,都是执行sql语句,比如JdbcTemplate,ReactiveRedisTemplate,ReactiveStringRedisTemplate

导入bean

  • ReactiveRedisTemplate<Object, Object>

    因为Redis中,都是以key和vlue的形式存储,此template执行都是object类型

  • ReactiveStringRedisTemplate

    键和值都是String类型的数据

# 操作Redis

在操作Redis之前,应该配置Redis的连接(这里使用阿里云的Redis)

spring: 
  redis:
    host: redis1202.redis.rds.aliyuncs.com
    username: qsyyke
    password: qsyyke:Cqy19981202
1
2
3
4
5

因为下面两个组件都已经在容器中,所以我们可以直接从容器中拿到

@Autowired
StringRedisTemplate redisTemplate;

@Autowired
ReactiveRedisTemplate reactiveRedisTemplate;
1
2
3
4
5

可以使用下面的方式操作Redis

ValueOperations<String, String> ops = redisTemplate.opsForValue();
ops.set("chuchen","chuchen");
1
2

# 切换连接工厂

Type of client to use. By default, auto-detected according to the classpath.
private ClientType clientType;
1
2

从上面可以看出,Redis的默认连接工厂是根据环境变的,也就是如果只提供了lettuce依赖,那么就使用它,如果只提供了Jedis,那么就使用它,但是如果两个都提供,那么我们可以在配置文件中,进行选择,导入spring的redis-stadter,就会导入有lettuce,所以可以默认使用的是lettuce

jedis依赖

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.6.1</version>
</dependency>
1
2
3
4
5
6