本文则针对数据库的连接配置作下简单的分析,方便笔者理解以及后续的查阅

栗子当先

以我们经常用的mybatis数据库持久框架来操作mysql服务为例


环境依赖

1.JDK v1.8+
2.springboot v2.0.3.RELEASE
3.mybatis v3.4.6
4.mysql v10.2.8-MarialDB

配置类步骤

1.pom.xml

<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>

2.springboot配置 application-datasource.properties

#datasource config
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/boot?useSSL=false&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimeZone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456 #mybatis config
mybatis.check-config-location=false
mybatis.mapper-locations=classpath*:database/mybatis/mapper/*.xml
mybatis.executor-type=reuse

代码栗子步骤

1.实体类User.java

package com.example.demo.database.entity;

/**
* @author nanco
* -------------
* demo-springboot
* -------------
* @create 2018/10/17 16:52
**/
public class User {
private Long id ; private String name ; private Integer age ; private String email ; public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} public String getEmail() {
return email;
} public void setEmail(String email) {
this.email = email;
} @Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
'}';
}
}

2.mapper配置文件 UserMapper.xml

<?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.example.demo.database.mysql.dao.UserDao"> <insert id="saveUser" parameterType="com.example.demo.database.entity.User">
insert into tbl_user(name,age,email)
values(#{name},#{age},#{email})
</insert>
</mapper>

3.为了使上述配置生效,则须定义扫描入口(@MapperScan)

package com.example.demo.database.config;

import com.mysql.jdbc.Driver;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration; /**
* @author nanco
* -------------
* demo-springboot
* -------------
* @create 2018/10/17 17:09
**/
@Configuration
public class DatabaseConfig { @Configuration
@ConditionalOnClass(Driver.class)
@MapperScan("com.example.demo.database.mysql.dao")
static class MysqlInterfaceScanner { }
}

4.来一个测试类

package com.example.demo.database.mysql;

import com.example.demo.database.DatabaseApplication;
import com.example.demo.database.entity.User;
import com.example.demo.database.mysql.dao.UserDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert; import javax.annotation.Resource; /**
* @author nanco
* -------------
* demo-springboot
* -------------
* @create 2018/10/17 17:13
**/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DatabaseApplication.class})
public class MysqlDaoTest { @Resource
private UserDao userDao; @Test
public void testSave() {
User user = new User();
user.setName("nanco");
user.setAge(18);
user.setEmail("nancoasky@gmail.com");
System.out.println(userDao.saveUser(user));
}
}

运行上述的测试案例便完成了简单的插入功能,其他的功能读者可自行编写

源码层

查阅了spring-boot-autoconfigure包下的spring.factories,发现对于数据源的配置是通过DataSourceAutoConfiguration类来进行的,由此简单的展开下

DataSourceAutoConfiguration

本类的注册是有条件的,其类上的注解是

@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })

其中DataSource类是JDK自带的,EmbeddedDatabaseType类则是依赖spring-jdbc包,本例中引入mybatis则默认带入了上述包。笔者按照@Configuration的加载顺序来对此类作下简单的分析

1.静态内部类注册解析


数据源检测

	@Configuration
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration { }

具体的代码读者可翻阅相应的文档,这里作下总结

  • 用户配置了spring.datasource.type属性或者classpath下存在springboot默认支持的数据源则该配置略过
  1. com.zaxxer.hikari.HikariDataSource
  2. org.apache.tomcat.jdbc.pool.DataSource
  3. org.apache.commons.dbcp2.BasicDataSource
  • 如果上述的条件不满足则会在classpath下找寻springboot默认支持的数据库驱动,存在则会创建SimpleDriverDataSource数据源用来创建数据库连接
  1. H2 Database
  2. Derby Database
  3. HSQL Database

本例中引入mybatis-spring-boot-starter便会引入spring-jdbc包,则会采用HikariDataSource数据源来获取数据库连接


数据源创建

	@Configuration
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration { }

根据用户配置的spring.datasource.type属性或者springboot默认支持的数据源(见上)来进行数据源对象的创建,具体的读者可自行阅读,本例中则会采取HikariDataSource数据源,并注入至bean工厂中,当然也可以通过配置项给予更多的属性配置

spring.datasource.hikari.max-wait-millis=10000
spring.datasource.hikari.min-idle=5
spring.datasource.hikari.initial-size=5
spring.datasource.hikari.validation-query=SELECT 1

2.导入类解析


数据库连接池状态类初始化

@Configuration
public class DataSourcePoolMetadataProvidersConfiguration { @Configuration
@ConditionalOnClass(HikariDataSource.class)
static class HikariPoolDataSourceMetadataProviderConfiguration { @Bean
public DataSourcePoolMetadataProvider hikariPoolDataSourceMetadataProvider() {
return (dataSource) -> {
if (dataSource instanceof HikariDataSource) {
return new HikariDataSourcePoolMetadata(
(HikariDataSource) dataSource);
}
return null;
};
} }
}

HikariDataSource为例,则会创建HikariDataSourcePoolMetadata对象,主要是用来获取连接池的相关信息,看下DataSourcePoolMetadata接口就行,具体如下

public interface DataSourcePoolMetadata {

	/**
* Return the usage of the pool as value between 0 and 1 (or -1 if the pool is not
* limited).
* <ul>
* <li>1 means that the maximum number of connections have been allocated</li>
* <li>0 means that no connection is currently active</li>
* <li>-1 means there is not limit to the number of connections that can be allocated
* </li>
* </ul>
* This may also return {@code null} if the data source does not provide the necessary
* information to compute the poll usage.
* @return the usage value or {@code null}
*/
Float getUsage(); /**
* Return the current number of active connections that have been allocated from the
* data source or {@code null} if that information is not available.
* @return the number of active connections or {@code null}
*/
Integer getActive(); /**
* Return the maximum number of active connections that can be allocated at the same
* time or {@code -1} if there is no limit. Can also return {@code null} if that
* information is not available.
* @return the maximum number of active connections or {@code null}
*/
Integer getMax(); /**
* Return the minimum number of idle connections in the pool or {@code null} if that
* information is not available.
* @return the minimum number of active connections or {@code null}
*/
Integer getMin(); /**
* Return the query to use to validate that a connection is valid or {@code null} if
* that information is not available.
* @return the validation query or {@code null}
*/
String getValidationQuery(); /**
* The default auto-commit state of connections created by this pool. If not set
* ({@code null}), default is JDBC driver default (If set to null then the
* java.sql.Connection.setAutoCommit(boolean) method will not be called.)
* @return the default auto-commit state or {@code null}
*/
Boolean getDefaultAutoCommit(); }

sql脚本执行加载

@Configuration
@Import({ DataSourceInitializerInvoker.class,
DataSourceInitializationConfiguration.Registrar.class })
class DataSourceInitializationConfiguration {
}

主要通过DataSourceInitializerInvoker类来进行sql脚本的执行加载,具体笔者就不贴代码了,作下简单的总结

  1. 如果spring.datasource.schema属性已指定相应的sql文件,则优先读取,并支持classpath路径查找
  2. 如果上述无配置,则默认读取classpath*:schema.sql/classpath*:schema-${platform}.sql文件(其中${platform}可用spring.datasource.platform指定)
  3. 如果没有上述文件,则不执行

温馨提示:如果想在环境运行的时候执行相应的sql语句,则仍需要另外配置用户名(spring.datasource.schema-username)与密码(spring.datasource.schema-password),方可执行

MybatisAutoConfiguration

其加载顺序是在上述的DataSourceAutoConfiguration之后的,看它头上的注解便可得知。读者在这之前最好已经了解了mybatis在spring中的相关用法,比如SqlsessionFactoryMappedStatement等基本概念,不了解也可直接戳笔者的先前文章Spring mybatis源码学习指引目录

springboot支持用户使用@MapperScan或者@Mapper注解来注册扫描相应的dao接口。后者只能应用于单个的数据源,一般推荐使用前者来进行扫描注册,因为前者有更多的属性配置。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan { /**
* Alias for the {@link #basePackages()} attribute. Allows for more concise
* annotation declarations e.g.:
* {@code @EnableMyBatisMapperScanner("org.my.pkg")} instead of {@code
* @EnableMyBatisMapperScanner(basePackages= "org.my.pkg"})}.
*/
String[] value() default {}; /**
* Base packages to scan for MyBatis interfaces. Note that only interfaces
* with at least one method will be registered; concrete classes will be
* ignored.
*/
String[] basePackages() default {}; /**
* Type-safe alternative to {@link #basePackages()} for specifying the packages
* to scan for annotated components. The package of each class specified will be scanned.
* <p>Consider creating a special no-op marker class or interface in each package
* that serves no purpose other than being referenced by this attribute.
*/
Class<?>[] basePackageClasses() default {}; /**
* The {@link BeanNameGenerator} class to be used for naming detected components
* within the Spring container.
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; /**
* This property specifies the annotation that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have
* the specified annotation.
* <p>
* Note this can be combined with markerInterface.
*/
Class<? extends Annotation> annotationClass() default Annotation.class; /**
* This property specifies the parent that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have
* the specified interface class as a parent.
* <p>
* Note this can be combined with annotationClass.
*/
Class<?> markerInterface() default Class.class; /**
* Specifies which {@code SqlSessionTemplate} to use in the case that there is
* more than one in the spring context. Usually this is only needed when you
* have more than one datasource.
*/
String sqlSessionTemplateRef() default ""; /**
* Specifies which {@code SqlSessionFactory} to use in the case that there is
* more than one in the spring context. Usually this is only needed when you
* have more than one datasource.
*/
String sqlSessionFactoryRef() default ""; /**
* Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean.
*
*/
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class; }

详细的属性注释也拷贝过来了,其可以针对不同的数据源扫描注册相应的dao接口,适用于多数据源应用。具体使用笔者就不展开了,具体看下其@MapperScan注解是如何被解析的,直接看MapperScannerRegistrar#registerBeanDefinitions()注册方法

  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
} Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
} Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
} Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
} Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
} scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}

说白了也就是通过mybatis的接口扫描类ClassPathMapperScanner类进行具体的处理,很简单。具体的解析步骤可详细戳上述的文章链接

小结

mybatis属性在springboot的使用,笔者并没有展开,读者可详看MybatisProperties类便可。笔者发现过多冗长的代码会影响笔者乃至读者的回看,于是笔者决定后续的文章,笔者尽可能详细记录自己的思考步骤,至于代码就贴出相应的关键部分即可方便后续回看~

springboot情操陶冶-web配置(六)的更多相关文章

  1. springboot情操陶冶-web配置(九)

    承接前文springboot情操陶冶-web配置(八),本文在前文的基础上深入了解下WebSecurity类的运作逻辑 WebSecurityConfigurerAdapter 在剖析WebSecur ...

  2. springboot情操陶冶-web配置(七)

    参数校验通常是OpenApi必做的操作,其会对不合法的输入做统一的校验以防止恶意的请求.本文则对参数校验这方面作下简单的分析 spring.factories 读者应该对此文件加以深刻的印象,很多sp ...

  3. springboot情操陶冶-web配置(四)

    承接前文springboot情操陶冶-web配置(三),本文将在DispatcherServlet应用的基础上谈下websocket的使用 websocket websocket的简单了解可见维基百科 ...

  4. springboot情操陶冶-web配置(二)

    承接前文springboot情操陶冶-web配置(一),在分析mvc的配置之前先了解下其默认的错误界面是如何显示的 404界面 springboot有个比较有趣的配置server.error.whit ...

  5. springboot情操陶冶-web配置(三)

    承接前文springboot情操陶冶-web配置(二),本文将在前文的基础上分析下mvc的相关应用 MVC简单例子 直接编写一个Controller层的代码,返回格式为json package com ...

  6. springboot情操陶冶-web配置(一)

    承接前文springboot情操陶冶-@SpringBootApplication注解解析,在前文讲解的基础上依次看下web方面的相关配置 环境包依赖 在pom.xml文件中引入web依赖,炒鸡简单, ...

  7. springboot情操陶冶-web配置(八)

    本文关注应用的安全方面,涉及校验以及授权方面,以springboot自带的security板块作为讲解的内容 实例 建议用户可直接路由至博主的先前博客spring security整合cas方案.本文 ...

  8. springboot情操陶冶-web配置(五)

    本文讲讲mvc的异常处理机制,方便查阅以及编写合理的异常响应方式 入口例子 很简单,根据之前的文章,我们只需要复写WebMvcConfigurer接口的异常添加方法即可,如下 1.创建简单的异常处理类 ...

  9. springboot情操陶冶-@SpringBootApplication注解解析

    承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上对@SpringBootApplication注解作下简单的分析 @SpringBootApplicat ...

随机推荐

  1. On the Optimal Approach of Survivable Virtual Network Embedding in Virtualized SDN

    Introduction and related work 云数据中心对于虚拟技术是理想的创新地方. 可生存性虚拟网络映射(surviavable virtual network embedding ...

  2. XP Sp3 开机就要激活,否则无法登录windows桌面

    参考网页:https://www.reddit.com/r/sysadmin/comments/5m9240/activating_windows_xp_in_2017_still_possible/ ...

  3. SpringSecurity实现权限管理和页面导航栏动态实现

    用户模块. 3 1.1  需求:获取用户名. 3 1.1.1     分析. 3 1.1.2     服务端获取用户信息. 4 1.1.3     页面获取用户信息. 5 1.2  给用户分配角色. ...

  4. Python3小知识点

    1. 是转义字符,\可以输出.在一行的末尾单独的一个\表示这行没有结束,下一行接着写 2.可以用"'或"""把一大段话引起来(可以换行)然后赋值,输出. 3.要 ...

  5. win10上使用Xshell通过ssh连接Linux

    Windows 10上现在能安装Linux子系统了,正好最近.Net Core也逐渐发展起来了,我也就在自己电脑上搞了一下 在Windows 10上安装Ubuntu的过程就不用说了,都是流程性的东西 ...

  6. java.net.ConnectException: Connection refused 异常

    错误信息: java.net.ConnectException: Connection refused at java.net.PlainSocketImpl.socketConnect(Native ...

  7. mysql 删除指定字符

    mysql 删除指定字符 1.1 前言        实际需求中如果想删除指定的字符,一般需要使用到trim函数.trim函数默认删除字符的前后空格,如果想指定删除特定字符,则需要使用一下语句进行声明 ...

  8. 清除SqlServer日志

    --在SQL2008中清除日志就必须在简单模式下进行,等清除动作完毕再调回到完全模式. USE [master]GO --GPSLocus是要清除日志的数据库名称ALTER DATABASE [DbN ...

  9. [Swift]LeetCode20. 有效的括号 | Valid Parentheses

    Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the inpu ...

  10. [Swift]LeetCode494. 目标和 | Target Sum

    You are given a list of non-negative integers, a1, a2, ..., an, and a target, S. Now you have 2 symb ...