2020:0702--11--SpringBoot与数据访问

605 阅读8分钟

主要内容

1.  JDBC
2.  整和Druid

1. Spring Boot 内部集成的 JDBC 模板访问 Mysql 数据库

1.1 Spring Boot访问 Mysql 数据库

1.  创建一个项目:

2.  连接虚拟机的数据库:mysql02
    创建一个测试数据库:jdbc

3.  获取数据源
    
    1.  配置文件 application.yml
        spring:
          datasource:
            username: root
            password: root
            url: jdbc:mysql://192.168.92.130:3306/jdbc
            driver-class-name: com.mysql.jdbc.Driver
    
        注意:
        driver 字符串原理用的 com.mysql.jdbc.Driver,
        新版已经变为 com.mysql.cj.jdbc.Driver。
            spring:
              datasource:
                username: root
                password: root
                url: jdbc:mysql://192.168.92.130:3306/jdbc
                driver-class-name: com.mysql.cj.jdbc.Driver
4.  注意
    
    默认使用的是 class com.zaxxer.hikari.HikariDataSource 数据源:
    class com.zaxxer.hikari.HikariDataSource
    
    默认的连接从HikariPool从取
    HikariProxyConnection@167670282 wrapping com.mysql.cj.jdbc.ConnectionImpl@149274cb

5. 测试一下
        @SpringBootTest
        class Day0702Springboot06DataJdbcApplicationTests {
        
            @Autowired
            DataSource dataSource;
            @Test
            void contextLoads() throws SQLException {
                System.out.println(dataSource.getClass());
        
                Connection connection = dataSource.getConnection();
        
                System.out.println(connection);
        
                connection.close();
            }
        
        }

2 探究一下数据源的自动配置的原理。

2.1 DataSourceConfiguration

    1.  背景相关

        1.  自动配置的数据源是:class com.zaxxer.hikari.HikariDataSource
        
        2.  数据源的相关配置都在这个类里面:DataSourceProperties

    2.  找到相关的自动配置类
    
        1.  org.springframework.book:spring-boot-autoconfigure:2.3.1.RELEASE
        
        2.  org.springframework.boot.autoconfigure.jdbc
            
        3.  org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration
        
        4.  org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

    3.  打开数据源配置类:DataSourceConfiguration
    
        org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration
        
        1.  关键注解:
        
        如果配置的spring.datasource.type是com.zaxxer.hikari.HikariDataSource,那么使用
        com.zaxxer.hikari.HikariDataSource:数据源。
        matchIfMissing = true,如果没有,默认你配了
        
        这就是为什么默认使用的是HikariDataSource数据源。

@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)

        2.  相关源码
abstract class DataSourceConfiguration {

	//tomcat.jdbc.pool.DataSource:数据源
	@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);

			return dataSource;
		}

	}

    //com.zaxxer.hikari.HikariDataSource:数据源
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(HikariDataSource.class)
	@ConditionalOnMissingBean(DataSource.class)
	
	//如果配置的spring.datasource.type是com.zaxxer.hikari.HikariDataSource,那么使用
	//com.zaxxer.hikari.HikariDataSource:数据源,matchIfMissing = true,如果没有,默认你配了
	@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);

			return dataSource;
		}

	}
        3.  我们可以通过: Spring.datasource.type指定数据源类型。

        4.  数据源配置类:DataSourceConfiguration
        根据配置创建数据源,默认使用HikariDataSource数据源。
    


        5.  指定自定义的数据源
    	@Configuration(proxyBeanMethods = false)
    	@ConditionalOnMissingBean(DataSource.class)
    	@ConditionalOnProperty(name = "spring.datasource.type")
    	static class Generic {
    
    		@Bean
    		DataSource dataSource(DataSourceProperties properties) {
    		    //使用构建者模式创建数据源
    			return properties.initializeDataSourceBuilder().build();
    		}
    
    	}
        6.  点进build()看一下
        
            BeanUtils工具类利用反射创建相应type的数据源,并且绑定相关属性
            public T build() {
                Class<? extends DataSource> type = this.getType();
                DataSource result = (DataSource)BeanUtils.instantiateClass(type);
                this.maybeGetDriverClassName();
                
                //绑定相关属性
                this.bind(result);
                return result;
            }

2.2 DataSourceAutoConfiguration

    1.  打开这个数据源自动配置类
        
        引入了一个:DataSourceInitializationConfiguration.class 

    2.  点进去,来到了DataSourceInitializationCon
    
        引入了一个:DataSourceInitializerInvoker.class
                    数据源初始化器的增强类

    3.  点进去来到了,DataSourceInitializerInvoker.class
        
        它是一个监听器:监听数据源创建事件的。

        方法一:afterPropertiesSet()
        会调用DataSourceInitializer类中的createSchema()方法
        
        作用:
            创建建表语句:createSchema()
            运行建表语句:runScripts(scripts, username, password);

       方法二:onApplicationEvent(DataSourceSchemaCreatedEvent event)
       监听到事件后,会执行这个方法
       调用DataSourceInitializer类中的initSchema()方法
       
       作用:
            调用initSchema()方法,初始化Schema
            runScripts(scripts, username, password); 运行插入数据的sql语句

    4.  那么我们将建表和插入数据的相关sql文件放进来就行了。
    
    5.   默认的规则:
        
        注意:DataSourceInitializerInvoker上的注释

        1.  建表的sql:命名为schema-*.sql
        
        2.  数据的sql:命名为data-*.sql
        
    6.  测试一下建表语句
    
        怎么样才能让我们的建表文件department.sql被加载呢
        
        在分析第3步时,我们找到DataSourceInitializerInvoker中的一个方法:afterPropertiesSet()
        
        它的作用:
            创建建表语句:createSchema()
            运行建表语句:runScripts(scripts, username, password);
            
    7.  我们点进createSchema();看一下
        
        创建建表语句时createSchema()的方法中,有一个获取相关建表资源的方法:getScripts()方法

    8.  点进getScripts()方法

2.3 注意:这就是规则

        这里面就指明了我们的建表文件department.sql被加载的规则:
            
            this.properties.getSchema() 
                它是dataSourceProperties中的属性:private List<String> schema;
                
            传给了getScripts()方法中的第二个参数:List<String> resources
                    
                如果resources为空,SpringBoot就去这两个路径找资源:
                    "classpath*:" + fallback + "-" + platform + ".sql"
                    "classpath*:" + fallback + ".sql"
                    传过来的fallback值:schema
                    
                即如果没有sql语句文件,去类路径下找   
            
                    schema-all.sql:DDL和DML语句
                    schema.sql:    DDL语句
                    
                如果不为空:就直接返回这个资源路径。    
                
                所以我们可以指定sql文件的路径位置List<String> resources,如果能获取到就直接返回。
	private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) {
	
	    //文件路径位置不为空,就直接返回。所以我么可以定义这个resources
	    //它正好是dataSourceProperties中的schema属性。
		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);
	}
        fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
        fallbackResources.add("classpath*:" + fallback + ".sql");
        
        其中platform是DataSourceProperties中的属性:值为"all"
            代表了DDL DML等sql语句

    9.  所以规则就是:
            
        1.  如果没有sql语句文件,去类路径下找   
            
            schema-all.sql:DDL和DML语句
            schema.sql:    DDL语句
            
        2.  如果我们想自己指定规则(用自己的命名规则)
            
            定义DataSourceProperties中的private List<String> schema;属性。
            
            
    10. 改名测试一下:

2.4 注意:*******************************

        配置中要开启一下:弹幕大神在线指导
        spring:
            datasource:
                initialization-mode: always

    11. 成功创建departement表

    12. 指定加载的位置
        
        如果我们就想加载命名为department.sql的文件名怎么办?
        定义DataSourceProperties中的private List<String> schema;属性。
        
        注意:
            schema是一个list属性,写法规则
            注意空格:空格不对会报错

        结果:建表成功

2.5 JdbcTemplateAutoConfiguration:导入操作数据库的两个工具

    如果有数据源Dataource和JdbcTemplate类时,会给我们import两个配置类
        JdbcTemplateConfiguration:
        NamedParameterJdbcTemplateConfiguration

    JdbcTemplateConfiguration:
        如果没有JdbcOperations类时,JdbcTemplateConfiguration会给容器添加一个jdbcTemplate组件
        
    
    NamedParameterJdbcTemplateConfiguration同理

2.6 测试一些数据库操作

    1.  注意如果sql文件还在类路径下,那么启动时还会在重新执行sql。就会将我们之前的数据覆盖掉。
    
    2.  HelloController
        @Controller
        public class HelloController {
        
            //自动注入:因为@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })条件满足
            @Autowired
            JdbcTemplate jdbcTemplate;
        
            @ResponseBody //把数据写出去
            @GetMapping("/query")
            public Map<String, Object> map(){
                List<Map<String, Object>> list = jdbcTemplate.queryForList("select * from department");
        
                return list.get(0);
            }
        }
    3.  因为表中数据被刷新了,我们再重新添加

    4.  执行主方法
    
    5.  发送请求测试
    
        return list.get(0);

3 测试其他数据源---使用druid数据源

3.1 背景

        HikariDataSource数据源虽说比druid数据源性能要高一些,但是druid有成套的数据源
        比如监控,包括安全的整个解决方法,所以用到druid也非常多。
        
        我们下面就来整和druid数据源

3.2 整和druid数据源

    1.  引入druid数据源坐标
    
    2.  在没有配置数据源时,默认使用HikariDataSource数据源
    
        在配置文件中指定数据源类型

    3.  测试一下有没有用druid数据源

3.3 对数据源进行配置

注意

    现在大多数Druid配置都是log4j作为logger,但是logback作为新一代的日志框架,
我们有理由使用logback配置Druid Filter,之前的配置是:
    spring.datasource.filters: stat,wall,log4j
    
    Druid支持配置多种Filter,配置信息保存在druid-xxx.jar!/META-INF/druid-filter.properties下面,
    具体如下:
    druid.filters.default=com.alibaba.druid.filter.stat.StatFilter
    druid.filters.stat=com.alibaba.druid.filter.stat.StatFilter
    druid.filters.mergeStat=com.alibaba.druid.filter.stat.MergeStatFilter
    druid.filters.counter=com.alibaba.druid.filter.stat.StatFilter
    druid.filters.encoding=com.alibaba.druid.filter.encoding.EncodingConvertFilter
    druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
    druid.filters.slf4j=com.alibaba.druid.filter.logging.Slf4jLogFilter
    druid.filters.commonlogging=com.alibaba.druid.filter.logging.CommonsLogFilter
    druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter
    druid.filters.wall=com.alibaba.druid.wall.WallFilter
    druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
    Druid支持配置多种Filter,其中就有Slf4jLogFilter
    
    众所周知,logback是slf4j的实现类,按照规定格式,改成下面就可以了:
    spring.datasource.filters=stat,wall,slf4j
#   数据源其他配置
    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,logback
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true  
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    1.  DataSourceProperties中没有这些属性
    
        所以直接这样配置是不能生效的
        
    2.  测试一下发现没有生效

    3.  要专门写一个配置类
    
        作用:
        
        将DruidDataSource中的属性用@ConfigurationProperties(prefix = "spring.datasource")和
        主配置文件中前缀是spring.datasource.属性名的配置,进行绑定。
        
        
        再用@Configuration将这个配置类生效
        
        那么主配置文件中的配置就会生效。
            @Configuration
            public class DruidConfig {
            
                //将druiddatasource中的属性和spring.datasource.属性名,配置进行绑定
            
                @ConfigurationProperties(prefix = "spring.datasource")
                @Bean
                public DataSource druid(){
                    return new DruidDataSource();
                }
            }
    4.  测试一下发现配置生效了

4. SpringBoot整合Druid小结

    导入druid依赖
    
    1.  注入数据源:
        spring:
          #数据源基本配置
          datasource:
            username: root
            password: root
            url: jdbc:mysql://192.168.92.130:3306/jdbc
            driver-class-name: com.mysql.cj.jdbc.Driver
    2.  指明数据源类型:Druid
        # 指明数据源类型
        type: com.alibaba.druid.pool.DruidDataSource
    3.  配置自动执行schema.sql
        # 开启后就能执行sql语句文件
        initialization-mode: always
    4.  自定义加载的schema.sql语句文件的位置和名字
        schema:
          - classpath*:department.sql
    5.  数据源的其他配置
        #   数据源其他配置
        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
    6.  配置监控统计拦截的filter
        filters: stat,wall,slf4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    7.  编写配置类,将上面的5,6步的配置和DruidDataSource绑定
        @Configuration
        public class DruidConfig {
        
            //将配置的spring.datasource属性和它进行绑定
            @ConfigurationProperties(prefix = "spring.datasource")
            @Bean
            public DataSource druid(){
                return new DruidDataSource();
            }
    8.  配置druid的监控(和第7步在一个类中)
            //配置druid的监控
            /**
             *   1. 配置一个管理后台的
             *      Servlet:ServletRegistrationBean,来注册StatViewServlet
             *      这个Servlet就是帮我们控制管理后台的Servlet
             */
            @Bean
            public ServletRegistrationBean statViewServlet(){
                ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        
                //初始化StatViewServlet的属性
                Map<String, String> initParams = new HashMap<String, String>();
                initParams.put("loginUsername","admin");
                initParams.put("loginPassword","123456");
                initParams.put("allow",""); //默认是允许所有访问
                initParams.put("deny","192.168.15.21");
        
                bean.setInitParameters(initParams);
        
                return bean;
            }
        
            /**
             * 2. 配置一个监控的filter
             */
            @Bean
            public FilterRegistrationBean webStatFilter(){
                FilterRegistrationBean bean = new FilterRegistrationBean();
        
                bean.setFilter(new WebStatFilter());
        
                //初始化WebStatFilter的属性参数
                Map<String, String> initParams = new HashMap<String, String>();
                initParams.put("exclusion", "*.js, *.css, /druid/*");
                //设置FilterRegistrationBean的属性
                bean.setInitParameters(initParams);
                bean.setUrlPatterns(Arrays.asList("/*"));
        
                return bean;
            }
        }
    9.  测试