目录
前言
在小白新手web开发简单总结(八)-数据库HSQLDB实例这个例子,因为并没有对数据库的连接和释放做太多的关注,只是为了能够先简单的去了解下web应用开发的流程。本次专门针对这部分总结下。
下面的代码是在小白新手web开发简单总结(八)-数据库HSQLDB实例这个例子,连接数据库的一个代码:
public Statement createStatement() {
try {
Class.forName("org.hsqldb.jdbcDriver");
} catch (ClassNotFoundException e) {
System.out.println("jdbcDriver not found!!!");
return null;
}
try {
//JDBC不支持自增,创建或连接数据库
connection = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
if (connection == null) {
return null;
}
//创建里面的表
Statement statement = connection.createStatement();
return statement;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
在这里使用的是DriverMananger的getConnection()建立与数据库的连接,但是与数据库的连接本来就是一个非常耗资源的操作,会产生很大的系统开销。所以就有了数据库的连接池。我觉得这个和我们在Java中的线程和线程池的概念是相同的,线程池也是为了减少频繁的创建销毁线程而诞生的,那么这个数据库的连接池就是为了能够减少频繁的建立和断开数据库而创建的。
一 什么是DataSource
web应用程序在访问数据库的时候,并不是直接访问数据库,而是通过数据库驱动来访问数据库的,这些数据库的驱动一般都是数据库的厂商提供。所以在Java EE中提供了JDBC接口的方式来访问数据库的驱动,从而访问数据库。
DataSource是一个数据库连接的容器,负责管理创建好的数据库的连接,当web应用在访问数据库的时候,不需要通过DriverManager来连接数据库,而是通过从DataSource中直接获得Connection对象,再来连接数据库。
需要配合数据库的驱动程序一起使用。通常有三种实现形式:
- (1)基本实现:就生成一个标准的Connection对象,在访问的时候,都会产生一个新的Connection,不支持连接池和分布式;
- (2)数据库连接池实现:生成自动参与连接池的Connection对象,支持连接池的连接,但不支持分布式;
- (3)分布式实现:生成一个Connection对象,该对象可用于分布式事务,大多数参与连接池连接。
不管哪种DataSource,都需要做一些配置:基本配置、关键配置、性能配置等:
基本配置主要包括的访问数据库的url、username、password、driverClass;
关键配置主要包括最小连接数、初始化连接数、最大连接数、最大等待时间;(第一种实现方式不需要)
性能配置主要包括预缓存设置、连接有效性检测、超时连接关闭设置等
二 常见的三种实现方式
下面分别简单的总结下每一种的应用,在小白新手web开发简单总结(八)-数据库HSQLDB实例提到的例子上进行优化。
1.基本实现
在Spring本身提供了org.springframework.jdbc.datasource.DriverManagerDataSource,可提供一个简单的数据库的连接Connection对象。每次在调用的getConnection()的时候,都会产生一个新的连接。比较适合单元测试或者简单的独立应用中。
- 因为DriverManagerDataSource本身就是springframework中自带的,需要在pom.xml中添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
- 使用小白新手web开发简单总结(八)-数据库HSQLDB实例中的jdbc.properties来配置数据库的url、user、password等,如下:
#hsqldb的相关配置
#最后会在apache/bin的目录下生成该数据库
jdbc.url = jdbc:hsqldb:file:db/hsqldb/xbook
jdbc.user = SA
jdbc.password =
jdbc.table = book
jdbc.driverClass=org.hsqldb.jdbcDriver
- 在resources/config下增加一个application-context-db.xml文件,里面进行配置DriverManagerDataSource这个JavaBean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--增加数据库配置的配置文件-->
<context:property-placeholder location="classpath:config/jdbc.properties"/>
<!--简单的一个连接,每次都会新建-->
<bean id="driverManagerDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driverClass}"/>
</bean>
</beans>
因为这个 DriverManagerDataSource每次都会创建一个新的Connection,并不是数据库连接池,所以不需要配置跟数据库连接池相关的参数。具体在项目中需要配置哪些重要参数可以提高性能等,以后在研究。
- 增加JdbcDataSource来获取这些DataSource实例
@Component
public class JdbcDataSource {
private DriverManagerDataSource driverManagerDataSource;
public void setDriverManagerDataSource(DriverManagerDataSource driverManagerDataSource{
this.driverManagerDataSource = driverManagerDataSource;
}
@PostConstruct
public void init() {
System.out.println("JdbcTemplate init 。。。。。。 ");
}
public DataSource createDriverManagerDataSource() {
return driverManagerDataSource;
}
}
- 那么同样也要在application-context-db.xml文件中增加DriverManagerDataSource与JdbcDataSource的依赖关系
<!--处理数据库连接的bean-->
<bean id="jdbcConfiguration" class="com.wj.hsqldb.datasource.JdbcDataSource">
<property name="driverManagerDataSource" ref="driverManagerDataSource"/>
</bean>
遗留问题: 为什么一定要添加setDriverManagerDataSource()方法才可以完成该的配置依赖关系 (我猜测大概率跟因为反射关系来绑定依赖关系有关,而反射过程中又是通过setxxx的方式来找到对应的依赖关系的属性)。
- 最后还需要在Spring项目的web.xml文件中,来读取这个 application-context-db.xml,让Spring的IoC容器来加载这些JavaBean。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/application-context*.xml</param-value>
</context-param>
经过这几步就可以直接在项目中使用 driverManagerDataSource这个DataSource实例了。那么就将之前com.wj.hsqldb.db.JdbcConfiguration通过DriverManager.getConnection()的方式替换成driverManagerDataSource.getConnection()。
2.数据库连接池实现
当一个web应用启动的时候,数据库连接池就会创建一定数量的数据库连接,并且维护着不少于最大数量的连接。当web应用在访问数据库的时候,就会从数据库连接池中找到一个未用的数据库连接,并把该连接标记为忙,当没有空闲连接的时候,就会创建新的数据库连接;当正在使用的数据库连接使用完之后,数据库的连接池就会将该连接标记为空闲,然后web应用在访问的时候,会再次可以使用。
从这个过程中可以看到其实和Java的线程池的工作机制是一样的,所以在理解这个地方的并不困难。所以也就看出DataSource的优势在于:
- 1)不需要频繁建立数据库的连接和断开,节省资源和时间;
- 2)不需要频繁的输入用户名和密码来连接数据库,节省内存和CPU开销
Java中开源的常用的数据库连接池有以下几种:
(1)DBCP
依赖Jakarta commons-pool对象池机制的数据库连接池。可直接在web应用中使用。在Tomcat中使用的就是DBCP。对应的实现类为org.apache.commons.dbcp.BasicDataSource。
在Spring项目使用过程中同DriverManagerDataSource,都是依次需要引入依赖、增加配置文件,然后在项目中就直接使用。
- 1)添加BasicDataSource的依赖
<!--DBCP数据源的配置-->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
- 2)在刚才在(1)基本实现中的application-context-db.xml添加BasicDataSource的JavaBean,以及添加到JdbcDataSource依赖关系
<!--配置DBCP数据源-->
<bean id="dbcpDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="maxActive" value="6"/>
<property name="maxIdle" value="3"/>
</bean>
<!--处理数据库连接的bean-->
<bean id="jdbcConfiguration" class="com.wj.hsqldb.datasource.JdbcDataSource">
<property name="dbcpDataSource" ref="dbcpDataSource"/>
<property name="driverManagerDataSource" ref="driverManagerDataSource"/>
</bean>
因为BasicDataSource创建的数据库的连接池,所以需要配置一些关键参数像最小连接数、初始化连接数等,这里先不做研究,后面随着项目逐渐去了解这些参数的配置对web应用访问数据库的性能的影响。
另外如果想在注意JdbcDataSource中添加BasicDataSource的这个属性,那么一定是在 (1)基本实现中添加的JdbcDataSource中添加两者的依赖关系,否则在这里会有两个JdbcDataSource实例,在另一一个实例中是无法使用没有定义的属性。
- 3)在JdbcDataSource增加相应的实例BasicDataSource
@Component
public class JdbcDataSource {
private BasicDataSource dbcpDataSource;
private DriverManagerDataSource driverManagerDataSource;
public void setDbcpDataSource(BasicDataSource dbcpDataSource) {
this.dbcpDataSource = dbcpDataSource;
}
public void setDriverManagerDataSource(DriverManagerDataSource driverManagerDataSource{
this.driverManagerDataSource = driverManagerDataSource;
}
@PostConstruct
public void init() {
System.out.println("JdbcTemplate init 。。。。。。 ");
}
public DataSource createBasicDataSource() {
return dbcpDataSource;
}
public DataSource createDriverManagerDataSource() {
return driverManagerDataSource;
}
}
经过这几步就可以直接在项目中使用 dbcpDataSource这个DataSource实例了。那么就将之前com.wj.hsqldb.db.JdbcConfiguration通过DriverManager.getConnection()的方式也替换成dbcpDataSource.getConnection(),并且现在创建的是一个拥有数据库连接池的Connection。
(2)C3P0
拥有比DBCP更丰富的配置,对应的实现类为com.mchange.v2.c3p0.ComboPooledDataSource。具体的使用方式同上,可参见项目中的application-context-db.xml和JdbcDataSource里面的相关代码。
(3)HikariCP
速度要比C3P0高,使用字节码更加简洁,可以加载更多代码到缓存中,实现一个无锁集合类型。在SpringBoot2中默认的使用的数据库连接池。
(4)Druid
阿里出品,淘宝和支付宝专用的数据库连接池,支持所有JDBC兼容的数据库,包括Oracle、MySql、Postgresql等,对应的实现类com.alibaba.druid.pool.DruidDataSource。替换DBCP和C3P0,提供了一个高效、功能强大、可扩展性好的数据库连接池。
包括三部分的内容:
- DruidDriver:代理Driver,可以监控数据库的访问性能;
- DruidDataSource:高效可管理的数据库连接池
- SQLParse:获取SQL执行日志。
具体使用方式同上,可参见项目中的application-context-db.xml和JdbcDataSource里面的相关代码。
那么配置完上面四个数据源之后,最后这些修改的文件的代码如下:
- 1)pom.xml添加的依赖为:
<!--jdbc接口 Spring对JDBC的封装-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- === 配置数据源 ==== -->
<!--c3p0数据源-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!--DBCP数据源的配置-->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.5</version>
</dependency>
- 2)application-context-db.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--增加数据库配置的配置文件-->
<context:property-placeholder location="classpath:config/jdbc.properties"/>
<!--简单的一个连接,每次都会新建-->
<bean id="driverManagerDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driverClass}"/>
</bean>
<!--数据库连接池-->
<!--配置C3P0数据源-->
<bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="initialPoolSize" value="3"/>
<property name="maxPoolSize" value="6"/>
</bean>
<!--配置DBCP数据源-->
<bean id="dbcpDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="maxActive" value="6"/>
<property name="maxIdle" value="3"/>
</bean>
<!--阿里的-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClass}"/>
</bean>
<!--处理数据库连接的bean-->
<bean id="jdbcConfiguration" class="com.wj.hsqldb.datasource.JdbcDataSource">
<property name="c3p0DataSource" ref="c3p0DataSource"/>
<property name="dbcpDataSource" ref="dbcpDataSource"/>
<property name="driverManagerDataSource" ref="driverManagerDataSource"/>
<property name="druidDataSource" ref="druidDataSource"/>
</bean>
</beans>
- 3)JdbcDataSource
@Component
public class JdbcDataSource {
private ComboPooledDataSource c3p0DataSource;
private BasicDataSource dbcpDataSource;
private DriverManagerDataSource driverManagerDataSource;
private DruidDataSource druidDataSource;
public void setC3p0DataSource(ComboPooledDataSource c3p0DataSource) {
this.c3p0DataSource = c3p0DataSource;
}
public void setDbcpDataSource(BasicDataSource dbcpDataSource) {
this.dbcpDataSource = dbcpDataSource;
}
public void setDriverManagerDataSource(DriverManagerDataSource driverManagerDataSource{
this.driverManagerDataSource = driverManagerDataSource;
}
public void setDruidDataSource(DruidDataSource druidDataSource) {
this.druidDataSource = druidDataSource;
}
@PostConstruct
public void init() {
System.out.println("JdbcTemplate init 。。。。。。 ");
}
public DataSource createComboPoolDataSource() {
return c3p0DataSource;
}
public DataSource createBasicDataSource() {
return dbcpDataSource;
}
public DataSource createDriverManagerDataSource() {
return driverManagerDataSource;
}
public DataSource createDruidDataSource() {
return druidDataSource;
}
}
相关的代码已经上传github:https://github.com/wenjing-bonnie/sql-web.git对应tag为example11下的相关代码(因为项目在一直更新,所以通过打tag的方式来标记这次代码)。
3.分布式实现
在一些复杂的web应用开发中,一个应用可能会涉及连接多个数据库,也就是多个数据源。列举两个常用的常见场景:
- 1)读写分离的数据库:读库只负责各种查询操作,写库负责增删改;
- 2)业务复杂:一个业务流程可能需要同时读取或修改两个数据库中的数据
这种多数据源的应用,就是典型的分布式场景。在Spring项目中,spring-jdbc提供了org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,其内部可以包含多个DataSource,可根据开发人员定义的规则在运行期间动态的访问当前的数据源。
这个AbstractRoutingDataSource具体在项目中怎么去用,我觉得后面根据项目去看下,现在先知道这个是可以加载多数据源,但是在项目中具体怎么用的,需要后面跟着项目去研究下。
遗留问题:多数据源的到底在项目中如何运用呢??
三 总结
在编写代码实例过程中遇到问题总结下:
1.Spring中的注解和xml配置JavaBean混合使用
在web应用开发中很难避免的会出现一部分JavaBean会通过注解来实例化,一部分JavaBean会通过xml来配置,那么就会出现在通过@Component中注解的类中怎么引用一个在xml配置的实例呢?
@AutoWrited是按照type自动注入的,那么@Resource就是按照name自动注入的,其中有两个重要的属性:name和type:如果不指定的这两个属性的话,会默认的按照name去查找
在@Resource中该name是去匹配配置的xml文件中的<bean name=“”/>,而不是<bean id=“”/>,如果没有找到符合的bean,则回退为一个原始类型进行查找;如果按照type属性去找,则会去找类型匹配的唯一的bean进行注入,找不到或者多个,都会抛出异常;如果两者都去匹配,则会去寻找唯一的bean进行匹配,找不到则抛出异常。
@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName自动注入罢了。@Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
例如在BookManagerJdbcService.java中要使用在xml配置的JdbcDataSource,则有如下代码
@Configuration
public class BookManagerJdbcService {
@Resource(type = JdbcDataSource.class, name = "jdbcDataSource")
private JdbcDataSource jdbcDataSource;
}
在 application-context-db.xml中的代码如下:
<!--处理数据库连接的bean-->
<bean id="jdbcDataSource" class="com.wj.hsqldb.datasource.JdbcDataSource" name="jdbcDataSource">
<property name="c3p0DataSource" ref="c3p0DataSource"/>
<property name="dbcpDataSource" ref="dbcpDataSource"/>
<property name="driverManagerDataSource" ref="driverManagerDataSource"/>
<property name="druidDataSource" ref="druidDataSource"/>
</bean>
这样就可以在BookManagerJdbcService.java中直接使用jdbcDataSource实例了。
2.web应用并不会直接访问数据库,而是通过JDBC接口的方式来访问数据库的驱动,从而访问数据库;
3.数据库的驱动一般有数据库厂商提供,不仅包含访问数据库的逻辑,还包括底层的网络通讯;
4.DataSource为数据源,就是为了连接数据库而提供数据库的连接;
5.DataSource分为三种类型:一种是每次只提供一个Connection;第二种是数据库连接池(类似线程池的概念);第三种为分布式数据源;
6.通常一个DataSource需要配置数据库的url、用户名、密码以及driverClass这些基本配置,以及一些性能配置和关键配置;
当然也有一些遗留问题需要后面去逐渐了解
1.为什么一定要添加setDriverManagerDataSource()方法才可以完成该的配置依赖关系
2.多数据源的到底在项目中如何运用呢??
下一篇去总结小白新手web开发简单总结(十二)-数据库连接的相关优化(事务管理)