数据源
JDBC数据源DriverManagerDataSource
主要参数如下:
- jdbcDriver:jdbc驱动
- url:数据库连接
- username:用户名
- password:密码
DBCP数据源BasicDataSource
该数据源依赖于 commons-dbcp.jar, commons-pool对象池机制的数据库连接池。BasicDataSource提供了close()方法关闭数据源,所以必须设定destroy-method=”close”属性, 以便Spring容器关闭时,数据源能够正常关闭。除以上必须的数据源属性外,还有一些常用的属性。
- defaultAutoCommit:设置从数据源中返回的连接是否采用自动提交机制,默认值为 true
- defaultReadOnly:设置数据源是否仅能执行只读操作, 默认值为 false
- maxActive:最大连接数据库连接数,设置为0时,表示没有限制
- maxIdle:最大等待连接中的数量,设置为0时,表示没有限制
- maxWait:最大等待秒数,单位为毫秒, 超过时间会报出错误信息
- validationQuery:用于验证连接是否成功的查询SQL语句,SQL语句必须至少要返回一行数据, 如你可以简单地设置为:“select count(*) from user”
- removeAbandoned:是否自我中断,默认是 false
- removeAbandonedTimeout:几秒后数据连接会自动断开,在removeAbandoned为true,提供该值
- logAbandoned:是否记录中断事件, 默认为 false
C3P0数据源ComboPooledDataSource
C3P0是一个开放源代码的JDBC数据源实现项目,它在lib目录中与Hibernate一起发布,实现了JDBC3和JDBC2扩展规范说明的 Connection 和Statement 池。C3P0类包c3p0-0.9.0.4.jar。C3P0拥有比DBCP更丰富的配置属性,通过这些属性,可以对数据源进行各种有效的控制。
HikariCP
官网:https://www.worldlink.com.cn/zh_tw/osdir/hikaricp.html HikariCP代码非常轻量,并且速度非常的快。 HikariCP在 spring-boot-starter-jdbc 中已经被引入,意味着它是spring默认推荐的数据源。
HikariConfig配置
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/simpsons");
config.setUsername("bart");
config.setPassword("51mp50n");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
HikariDataSource ds = new HikariDataSource(config)
HikariDataSource直接配置
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://localhost:3306/simpsons");
ds.setUsername("bart");
ds.setPassword("51mp50n");
使用配置文件
HikariConfig config = new HikariConfig("/some/path/hikari.properties");
HikariDataSource ds = new HikariDataSource(config);
使用java.util.Properties
Properties props = new Properties();
props.setProperty("dataSourceClassName", "org.postgresql.ds.PGSimpleDataSource");
props.setProperty("dataSource.user", "test");
props.setProperty("dataSource.password", "test");
props.setProperty("dataSource.databaseName", "mydb");
props.put("dataSource.logWriter", new PrintWriter(System.out));
HikariConfig config = new HikariConfig(props);
HikariDataSource ds = new HikariDataSource(config);
HikariCP配置说明
必要的属性
属性 | 说明 | 默认值 |
dataSourceClassName | JDBC驱动程序提供的数据源类的名称。注意:不支持XA数据源。如果使用jdbcUrl配置,则不需配置此属性 | null |
jdbcUrl | jdbc链接url | null |
username | 数据库用户名 | null |
password | 数据库密码 | null |
driverClassName | 数据库驱动 | null |
常用属性
属性 | 说明 | 默认值 |
autoCommit | 控制从池返回的连接的默认自动提交行为 | true |
connectionTimeout | 连接超时时间,超出此时间将抛出SQLException。支持最低250ms | 默认值:30000(30秒) |
idleTimeout | 空闲连接存活的最长时间。只在minimumIdle < maximumPoolSize时有效。即当前连接数小于minimumIdle 时存活的连接不会失效。最小允许值为10000ms(10秒)。0表示不失效 | 默认值:600000(10分钟) |
maxLifetime | 连接的最大生存期。正在使用的连接不会失效。它也遵从idleTimeout 的设置 | 默认值:1800000(30分钟) |
connectionTestQuery | 用于检查连接是否有效的sql查询语句(例如:select 1 from dual)。适用于JDBC4以前的驱动程序。建议您先不要设置,如果你的驱动程序不是JDBC4,HikariCP会报错,否则会正常运行。 | 无 |
minimumIdle | 允许的空闲连接的最小数量 | 默认值:与maximumPoolSize相同(注意:最好是小于最大值) |
maximumPoolSize | 允许的连接数最大值(包括空闲连接+正在使用的连接)。当连接数到达该数量,则等待connectionTimeout毫秒,超出则抛出异常。 | 默认值:10 |
poolName | 此属性表示连接池的用户定义名称,主要出现在日志和JMX管理控制台中,用于标识池和池配置。 | 默认值:自动生成 |
不常用属性
还有很多不常用属性,请自行查询官网把。
Druid
Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。
Github项目地址 https://github.com/alibaba/druid 文档 https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98 下载 http://repo1.maven.org/maven2/com/alibaba/druid/ 监控DEMO http://120.26.192.168/druid/index.html
Druid对比HikariCP
HikariCP简洁,速度快,Druid功能强大,二者侧重点不同,如果你不需要过多的功能,只专注于提供连接池的话,推荐使用HikariCP。
友情提示:使用Druid如果不使用它的监控功能,请做如下配置
# 禁用druid监控,如果启用,请设置用户名密码(否则会有未授权的漏洞),默认是true
spring.datasource.druid.stat-view-servlet.enabled=false
Spring的DAO抽象
- JdbcDaoSupport:JDBC DAO抽象类。开发者需要为它设置数据源(DataSource),通过其子类,可以获得JdbcTemplate来访问数据库。
- HibernateDaoSupport:Hibernate DAO抽象类。开发者需要配置Hibernate sessionFactory,通过其子类,可以获得HibernateTemplate来访问数据库。HibernateTemplate中有execute和executeFind两个方法接受一个HibernateCallback接口回调,在该接口中的doInHibernate方法中可以直接使用Session,以及其涉及到的HibernateAPI如Query对象等对象。
- JdoDaoSupport:String为JDO提供的DAO抽象类,开发者需要为它配置PersistenceManagerFactory,通过其子类,可以获得JdoTemplate来访问数据库。
JdbcTemplate + HikariCP数据源示例
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yyoo.mytest</groupId>
<artifactId>springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- spring-boot-starter-parent是一个特殊的启动器,他可以使我们在下面定义jar依赖时,不用提供版本标签 version -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
</parent>
<dependencies>
<!-- 添加web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring jdbc相关依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
可以看到与我们之前的依赖相比,就只多了spring-boot-starter-jdbc依赖和mysql的驱动依赖
Properties文件
jdbcUrl=jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
username=用户名
password=密码
driverClassName=com.mysql.cj.jdbc.Driver
中间汉字部分请自行替换
创建数据库表
我们使用mysql创建如下两张表,用于示例展示
表名:t_test,有3个字段。
表名:t_demo,有3个字段
配置HikariCP数据源
package com.yyoo.boot.dao;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import javax.sql.DataSource;
import java.io.IOException;
@Configuration
@ComponentScan("com.yyoo.boot.dao")
public class DaoConfig {
@Value("classpath:dao.properties")
private Resource configResource;
@Bean
public DataSource getDriverManagerDataSource() throws IOException {
//-- 此处使用了Resource ,请查看Resource章节
String propertiesPath = configResource.getFile().getAbsolutePath();
HikariConfig hikariConfig = new HikariConfig(propertiesPath);
HikariDataSource ds = new HikariDataSource(hikariConfig);
return ds;
}
}
测试代码(先验证数据源)
package com.yyoo.boot.dao;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javax.sql.DataSource;
public class Demo1 {
@Test
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(DaoConfig.class);
context.refresh();
context.registerShutdownHook();
DataSource dataSource = context.getBean(DataSource.class);
System.out.println(dataSource);
}
}
建议在架构搭建的过程中进行分段验证,避免一次写完所有配置,再执行。否则如果出现错误或问题,你的架构代码半天也启动不起来。
注意:context.registerShutdownHook();的作用,如果没有该语句,spring在程序执行完成(容器关闭时)不会回调关闭相应的资源(如:连接池的释放等)。所以context.registerShutdownHook();还是十分有必要的。
配置JdbcTemplate
在配置类中添加JdbcTemplate的配置
package com.yyoo.boot.dao;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.io.IOException;
@Configuration
@ComponentScan("com.yyoo.boot.dao")
public class DaoConfig {
@Value("classpath:dao.properties")
private Resource configResource;
@Bean
public DataSource getDriverManagerDataSource() throws IOException {
String propertiesPath = configResource.getFile().getAbsolutePath();
HikariConfig hikariConfig = new HikariConfig(propertiesPath);
HikariDataSource ds = new HikariDataSource(hikariConfig);
return ds;
}
/**
*
* 因为JdbcTemplate一旦创建就是线程安全的,所以我们可以将它定义为一个单例的bean
*
*/
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}
JdbcTemplate用了ThreadLocal,使各线程能够保持各自独立的一个对象,其实就是一个变量副本,实现了线程安全。 JdbcTemplate类的实例是线程安全的实例。这一点非常重要,正因为如此,你可以配置一个简单的JdbcTemplate实例,并将这个“共享的”、“安全的”实例注入到不同的DAO类中去。 另外, JdbcTemplate 是有状态的,因为他所维护的DataSource 实例是有状态的,但是这种状态是无法变化的。 一旦JdbcTemplate被创建,他是一个线程安全的对象。 一个你需要创建多次JdbcTemplate实例的理由可能在于,你的应用需要访问多个不同的数据库,从而需要不同的DataSources来创建不同的JdbcTemplates实例。
编写dao类
TestDao和TestDaoImpl
package com.yyoo.boot.dao.dao;
import com.yyoo.boot.dao.beans.TestBean;
public interface TestDao {
int insert(TestBean bean);
int update(TestBean bean);
TestBean getTestBeanById(int id);
}
package com.yyoo.boot.dao.dao;
import com.yyoo.boot.dao.beans.TestBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Repository
public class TestDaoImpl implements TestDao {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public int insert(TestBean bean) {
return jdbcTemplate.update("insert into t_test(id,name,remark) values(?,?,?)",
bean.getId(),bean.getName(),bean.getRemark());
}
@Override
public int update(TestBean bean) {
return jdbcTemplate.update("update set t_test name = ?,remark=? where id = ?",
bean.getId(),bean.getName(),bean.getRemark());
}
@Override
public TestBean getTestBeanById(int id) {
return jdbcTemplate.queryForObject("select id,name,remark from t_test where id = "+id,TestBean.class);
}
}
DemoDao和DemoDaoImpl
package com.yyoo.boot.dao.dao;
import com.yyoo.boot.dao.beans.DemoBean;
public interface DemoDao {
int insert(DemoBean bean);
int update(DemoBean bean);
DemoBean getTestBeanById(int id);
}
package com.yyoo.boot.dao.dao;
import com.yyoo.boot.dao.beans.DemoBean;
import com.yyoo.boot.dao.beans.TestBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Repository
public class DemoDaoImpl implements DemoDao {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public int insert(DemoBean bean) {
return jdbcTemplate.update("insert into t_demo(id,`key`,`value`) values(?,?,?)",
bean.getId(),bean.getKey(),bean.getValue());
}
@Override
public int update(DemoBean bean) {
return jdbcTemplate.update("update set t_demo `key` = ?,`value`=? where id = ?",
bean.getId(),bean.getKey(),bean.getValue());
}
@Override
public DemoBean getTestBeanById(int id) {
return jdbcTemplate.queryForObject("select id,`key`,`value` from t_demo where id = "+id, DemoBean.class);
}
}
注:
- key、value是保留关键字,我们的sql字符串使用了反引号,如果不使用JdbcTemplate会报错。
- 我们看到两个Dao实现类都使用了 @Resource 注入了一个JdbcTemplate引用。其实我们可以定义一个BaseDao和BaseDaoImpl,在BaseDaoImpl中注入一次JdbcTemplate引用即可,其他的DaoImpl继承该BaseDaoImpl即可。
定义一个测试Service
package com.yyoo.boot.dao.service;
import com.yyoo.boot.dao.beans.DemoBean;
import com.yyoo.boot.dao.beans.TestBean;
public interface MyService {
void insert(TestBean test, DemoBean demo);
}
package com.yyoo.boot.dao.service;
import com.yyoo.boot.dao.beans.DemoBean;
import com.yyoo.boot.dao.beans.TestBean;
import com.yyoo.boot.dao.dao.DemoDao;
import com.yyoo.boot.dao.dao.TestDao;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class MyServiceImpl implements MyService {
@Resource
private TestDao test1Dao;
@Resource
private DemoDao demoDao;
@Override
public void insert(TestBean test, DemoBean demo) {
test1Dao.insert(test);
demoDao.insert(demo);
}
}
我们此处只定义了一个insert方法,方法中同事insert了两个表,个一条数据。如要验证其他Dao方法,请自己补全把。
测试demo
package com.yyoo.boot.dao;
import com.yyoo.boot.dao.beans.DemoBean;
import com.yyoo.boot.dao.beans.TestBean;
import com.yyoo.boot.dao.service.MyService;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javax.sql.DataSource;
public class Demo1 {
@Test
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(DaoConfig.class);
context.refresh();
context.registerShutdownHook();
DataSource dataSource = context.getBean(DataSource.class);
System.out.println(dataSource);
MyService service = context.getBean(MyService.class);
TestBean bean = new TestBean();
bean.setId(1);
bean.setName("测试名称");
bean.setRemark("测试备注");
DemoBean demo = new DemoBean();
demo.setId(1);
demo.setKey("demokey");
demo.setValue("demovalue");
service.insert(bean,demo);
}
}
完整的代码结构
示例Service中,如果 test1Dao.insert(test); 之后demoDao.insert(demo);之前有其它业务逻辑代码,而且在这些逻辑代码中出现了异常,我们目前的代码会出现t_test表插入成功而t_demo表插入失败的问题。这显然是不符合业务逻辑的。我们将在下一章介绍此涉及到的事务问题。