1,概念
1)数据库连接
java应用程序可以通过JDBC或Hibernate对数据库系统进行访问。JDBC或Hibernate提供了事务控制的接口,这些接口把事务控制相关的命令发送给数据库系统,由数据库系统来控制事务的隔离级别。
一般来说,java 应用程序访问数据库的过程是:
- 加载数据库驱动程序;
- 通过jdbc 建立数据库连接;
- 访问数据库,执行sql 语句;
- 断开数据库连接。
2)数据库连接池(Connection Pool)
常用的数据库连接池都实现了DataSource接口。
- 优点
对数据库连接进行复用,从而减少了重复创建和删除连接的开销,提高了系统的性能和可扩展性。 - 缺点
连接池会占用一定的内存和资源,在高并发环境中会出现连接池满的情况。需要合理设置连接池的参数。
1>HikariCP(推荐)
- 快速启动、低延迟。史上最快连接池。
hikariCP的高性能得益于最大限度的避免锁竞争。 - 支持各种JDBC驱动程序和数据源。
- 在springboot2.0之后,采用的默认数据库连接池就是Hikari。优先级>tomcat jdbc pool>dbcp2
2>Druid
- 阿里巴巴开发的开源连接池;目前最好的数据库连接池。
- 功能强大:面向监控的连接池,内置功能丰富、同时可扩展;
- 支持JDBC和非JDBC数据源。
3>Apache Commons DBCP
@Configuration
public class DataSourceConfig {
BasicDataSource ds = new BasicDataSource ();
//其它与C3P0一样
return ds;
}
常见属性 | 说明 |
---|---|
initialSize=“10” | 初始化连接,连接池启动时创建的初始化连接数量(默认值为0) |
maxActive=“80” | 最大活动连接,连接池中可同时连接的最大的连接数(默认值为8) |
minIdle=“10” | 最小空闲连接,连接池中最小的空闲的连接数,低于这个数量会被创建新的连接(默认为0,该参数越接近maxIdle,性能越好,因为连接的创建和销毁,都是需要消耗资源的;但是不能太大,因为在机器很空闲的时候,也会创建低于minidle个数的连接,类似于jvm参数中的Xmn设置) |
maxIdle=“60” | 最大空闲连接,连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负数表示不限制(默认为8个,maxIdle不能设置太小,因为假如在高负载的情况下,连接的打开时间比关闭的时间快,会引起连接池中idle的个数上升超过maxIdle,而造成频繁的连接销毁和创建,类似于jvm参数中的Xmx设置) |
maxWait=“3000” | 从池中取连接的最大等待时间,单位ms.当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待(默认为无限) |
注意事项:
maxIdle值与maxActive值应配置的接近:
当连接数超过maxIdle值后,刚刚使用完的连接(刚刚空闲下来)会立即被销毁。而不是想要的空闲M秒后再销毁起一个缓冲作用。若maxIdle与maxActive相差较大,在高负载的系统中会导致频繁的创建、销毁连接,连接数在maxIdle与maxActive间快速频繁波动,这不是想要的。高负载系统的maxIdle值可以设置为与maxActive相同或设置为-1(-1表示不限制),让连接数量在minIdle与maxIdle间缓冲慢速波动。
timeBetweenEvictionRunsMillis建议设置值:
minIdle要与timeBetweenEvictionRunsMillis配合使用才有用,单独使用minIdle不会起作用。
initialSize=“5”,会在tomcat一启动时,创建5条连接,效果很理想。但同时我们还配置了minIdle=“10”,也就是说,最少要保持10条连接,那现在只有5条连接,哪什么时候再创建少的5条连接呢?
1、等业务压力上来了, DBCP就会创建新的连接。
2、配置timeBetweenEvictionRunsMillis=“时间”,DBCP会启用独立的工作线程定时检查,补上少的5条连接。销毁多余的连接也是同理。
连接销毁的逻辑:
DBCP的连接数会在initialSize - minIdle - maxIdle - maxActive 之间变化。变化的逻辑描述如下:
默认未配置initialSize(默认值是0)和timeBetweenEvictionRunsMillis参数时,刚启动tomcat时,连接数是0。当应用有一个并发访问数据库时DBCP创建一个连接。目前连接数量还未达到minIdle,但DBCP也不自动创建新连接已使数量达到minIdle数量(没有一个独立的工作线程来检查和创建)。随着应用并发访问数据库的增多,连接数也增多,但都与minIdle值无关,很快minIdle被超越,minIdle值一点用都没有。直到连接的数量达到maxIdle值,这时的连接都是只增不减的。 再继续发展,连接数再增多并超过maxIdle时,使用完的连接(刚刚空闲下来的)会立即关闭,总体连接的数量稳定在maxIdle但不会超过maxIdle。
但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。这时如果应用业务压力小了,访问数据库的并发少了,连接数也不会减少(没有一个独立的线程来检查和销毁),将保持在maxIdle的数量。
默认未配置initialSize(默认值是0),但配置了timeBetweenEvictionRunsMillis=“30000”(30秒)参数时,刚启动tomcat时,连接数是0。马上应用有一个并发访问数据库时DBCP创建一个连接。目前连接数量还未达到minIdle,每30秒DBCP的工作线程检查连接数是否少于minIdle数量,若少于就创建新连接直到达到minIdle数量。
随着应用并发访问数据库的增多,连接数也增多,直到达到maxIdle值。这期间每30秒DBCP的工作线程检查连接是否空闲了30分钟,若是就销毁。但此时是业务的高峰期,是不会有长达30分钟的空闲连接的,工作线程查了也是白查,但它在工作。到这里连接数量一直是呈现增长的趋势。
当连接数再增多超过maxIdle时,使用完的连接(刚刚空闲下来)会立即关闭,总体连接的数量稳定在maxIdle。停止了增长的趋势。但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。
这时如果应用业务压力小了,访问数据库的并发少了,每30秒DBCP的工作线程检查连接(默认每次查3条)是否空闲达到30分钟(这是默认值),若连接空闲达到30分钟,就销毁连接。这时连接数减少了,呈下降趋势,将从maxIdle走向minIdle。当小于minIdle值时,则DBCP创建新连接已使数量稳定在minIdle,并进行着新老更替。
4>Apache DBCP2
- 开源连接池;
- 支持JDBC1、JDBC2、JDBC3规范和JDBC的扩展API;
- 相比C3P0,DBCP的性能略有下降,但是在一些场景下,DBCP比C3P0更加稳定。
5>C3P0
- 开源连接池。
- 单线程的,在高并发的环境下性能会降低。
- 它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
i>配置 C3P0 信息
- application.properties
# SQLServer 数据库配置信息
c3p0.jdbcUrl=jdbc:sqlserver://localhost:1433;DatabaseName=game
c3p0.user=gm
c3p0.password=root
c3p0.driverClass=com.microsoft.sqlserver.jdbc.SQLServerDriver
c3p0.minPoolSize=2
c3p0.maxPoolSize=10
c3p0.maxIdleTime=30
c3p0.checkoutTimeout=30000
c3p0.acquireIncrement=3
c3p0.maxStatements=1000
c3p0.initialPoolSize=3
c3p0.idleConnectionTestPeriod=60
c3p0.acquireRetryAttempts=30
c3p0.acquireRetryDelay=1000
c3p0.breakOnAcquireFailure=true
c3p0.breakAfterAcquireFailure=false
c3p0.testConnectionOnCheckout=false
引入 C3P0 和 SQLServer 依赖:
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>7.0.0.jre8</version>
</dependency>
<!-- 引入 dbutils 依赖操作数据库 -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
创建 C3P0 配置类:
@Configuration
public class C3p0Configuration {
@Bean(name = "dataSource")
@Primary // 用 @Primary 区分主数据源
@ConfigurationProperties(prefix = "c3p0") // 指定配置文件中,前缀为 c3p0 的属性值
public DataSource dataSource(){
return DataSourceBuilder.create()
.type(ComboPooledDataSource.class).build();
}
}
ii>C3P0 多数据源配置:
@Configuration
@ConfigurationProperties(prefix = "spring.data.neo4j")
public class DataSourceConfig {
String driverClassName;
String jdbcUrl;
String username;
String password;
String type;
@Bean(name = "neo4jDataSource")
public DataSource neo4jDataSource() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driverClassName);
ds.setUser(username);
ds.setPassword(password);
ds.setJdbcUrl(jdbcUrl);
//根据实际需要配置各种参数。
return ds;
}
}
yml文件中配置:
spring
data:
neo4j:
driver-class-name: org.neo4j.jdbc.Driver
jdbc-url: jdbc:neo4j:bolt://postgresql.host:7687
uri: bolt://postgresql.host:7687
username: neo4j
password: ${spring.data.neo4j.password}
type: ${spring.datasource.type}
//使用Spring的注入功能,可以把DataSource(数据库连接池)注册到jdbcTemplate中。
@Configuration
@EnableTransactionManagement
public class Neo4jJdbcConfig {
@Autowired
//Qualifier指明具体的实现类
@Qualifier("neo4jDataSource")
DataSource neo4jDataSource;
@Bean(name = "neo4jJdbcTemplate")
public JdbcTemplate neo4jJdbcTemplate() {
return new JdbcTemplate(neo4jDataSource);
}
}
//使用JdbcTemplate时,如果是多数据源
@Autowired
@Qualifier("neo4jJdbcTemplate")
private JdbcTemplate neo4jJdbcTemplate;
6>tomcat jdbc pool
Tomcat 从 7.0 开始引入一个新的模块:Tomcat jdbc pool,在tomcat-jdbc包下。
- 近乎兼容 dbcp ,性能更高
- 异步方式获取连接,支持高并发应用环境
- 超简单,核心文件只有8个,比 c3p0 还少
2,JDBC(Java Data Base Connectivity,java数据库连接)
1)概念
- jdbc是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问;
2)使用
- 通过java.sql中的Connection连接数据库;
- 创建Statement或PreparedState执行SQL语句;
- 通过ResultSet获取返回结果集。
Class.forName("org.h2.Driver");
try(
Connection con = DriverManager.getConnection("jdbc:h2:mem:test_db");
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select x from ...")
){
while(resultSet.next()){
log.info(resultSet.getInt(1));
}
}catch(SQLException e){
//handle SQL exceptions, perform tansaction rollback
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class JdbcConnection {
public void connection() {
PreparedStatement preparedStatement = null;
Connection connection = null;
ResultSet resultSet = null;
try {
//初始化对象
//1.加载驱动 不同的数据库对应的驱动不同,同时需要引入lib/*.jar包或添加对应的maven依赖
Class.forName("org.postgresql.Driver");
//获取数据库连接。三个参数为:url、user、psw 不同数据库url格式不同
connection = DriverManager.getConnection("jdbc:postgresql://127.0.0.1:5432/databaseName", "postgres", "psw");
/**
* 执行对应的sql语句
* connection常用的方法有:
* 1. createStatement() 创建向数据库发送sql的statement对象。
* 2. prepareStatement(sql) 创建向数据库发送预编译sql的PrepareSatement对象。
* 3. prepareCall(sql) 创建执行存储过程的callableStatement对象。
* 4. setAutoCommit(boolean autoCommit) 设置事务是否自动提交。
* 5. commit() 在链接上提交事务。
* 6. rollback() 在此链接上回滚事务。
*/
preparedStatement = connection.prepareStatement("create database lwhtest");
/**
*
*/
boolean result = preparedStatement.execute();
if (result) {
System.out.println("create a connection success!");
} else {
System.out.println("create a connection fail!");
}
} catch (Exception e) {
// TODO: handle exception
System.out.println("error!");
} finally {
//释放资源:ResultSet, Statement和Connection对象
try {
if (preparedStatement != null)
preparedStatement.close();
if (connection != null)
connection.close();
} catch (Exception e2) {
// TODO: handle exception
}
}
}
}
3)Statement
- 用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。
- 用于执行不带参数的简单SQL语句。
带参数的sql执行用PreparedStatement代替,预编译防止sql注入。 - 每次执行sql语句,数据库都要执行sql语句的编译
最好用于仅执行一次查询并返回结果的情形。
单次执行效率高于PreparedStatement,批量sql执行由于PreparedStatement预编译效率更高。
方法 | 含义 |
---|---|
executeQuery(String sql) | 用于向数据发送查询语句。 |
executeUpdate(String sql) | 用于向数据库发送insert、update或delete语句 |
execute(String sql) | 用于向数据库发送任意sql语句 |
addBatch(String sql) | 把多条sql语句放到一个批处理中。 |
executeBatch() | 向数据库发送一批sql语句执行。 |
PreparedStatement pst=con.prepareStatement(“select * from users where name=? and password=?”);
pst.setString(1, username);
pst.setString(2, password);
ResultSet rst=pst.executeQuery();
4)CallableStatement接口
扩展了 PreparedStatement(父接口),用来调用存储过程,它提供了对输出和输入/输出参数的支持。
5)ResultSet
ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。(ResultSet跟普通的数组不同,索引从1开始而不是从0开始);
1>获取行
ResultSet提供了对结果集进行滚动的方法:
next():移动到下一行
Previous():移动到前一行
absolute(int row):移动到指定行
beforeFirst():移动resultSet的最前面。
afterLast() :移动到resultSet的最后面。
ResultSet rs = null;
//4.向数据库发sql,并获取代表结果集的resultset
String sql = "select id,name,password,email,birthday from users";
rs = st.executeQuery(sql);
//5.取出结果集的数据
rs.afterLast();
rs.previous();
System.out.println("id=" + rs.getObject("id"));
System.out.println("name=" + rs.getObject("name"));
System.out.println("password=" + rs.getObject("password"));
System.out.println("email=" + rs.getObject("email"));
System.out.println("birthday=" + rs.getObject("birthday"));
2>获取值
ResultSet既然用于封装执行结果的,所以该对象提供的都是用于获取数据的get方法:
// 获取任意类型的数据
getObject(int index)
getObject(string columnName)
//获取指定类型的数据,例如:
getString(int index)
getString(String columnName)
3,Hibernate
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使java程序员可以随心所欲地使用对象编程思想来操纵数据库。目前使用较少。
4,JdbcTemplate
JdbcTemplate是Spring对JDBC的封装,目的是使JDBC更加易于使用。
JdbcTemplate处理了资源的建立和释放。
1)管理JDBC连接
使用连接池配置。
2)执行SQL语句
在JdbcTemplate中执行SQL语句的方法大致分为3类:
1>execute
- 可以执行所有SQL语句
- 没有返回值,一般用于执行DDL语句。
2>queryXxx
用于DQL数据查询语句。
查询单行:
String sql = "select num,name,age from student where id = ?";
RowMapper<stu> rowMapper = new BeanPropertyRowMapper<stu>(stu.class);
stu s = jdbcTemplate.queryForObject(sql, rowMapper,5);//最后一个参数为id值
查询多行:
String sql = "select num,name,age from student where id > ?";
RowMapper<stu> rowMapper = new BeanPropertyRowMapper<stu>(stu.class);
List<stu> s = jdbcTemplate.query(sql, rowMapper,0);//最后一个参数为id值
单值查询:
String sql = "select count(name) from student";
Long count = jdbcTemplate.queryForObject(sql, Long.class);
3>update
- 用于执行INSERT、UPDATE、DELETE等DML语句。
- 预编译、多参数
String sql = "update student set name = ? where num = ?";
jdbcTemplate.update(sql, "pink",0001);
3)批量插入:batchUpdate
- 开启批处理模式:
在数据库地址上加上rewriteBatchedStatements=true
;这样访问数据库只需要一个SqlSession。
减少往返数据库的次数,提高数据库访问性能。 - 预编译提高sql执行效率
预编译就是将sql中的值通过占位符写入,sql语句模板化或者说参数化。一次编译、多次运行,省去了解析优化等过程。
注意:预编译(useServerPrepStmts)和预编译缓存(cachePrepStmts)一定要同时开启或同时关闭。否则会影响执行效率
import org.springframework.jdbc.core.JdbcTemplate;
@Autowired
JdbcTemplate jdbcTemplate;
//返回值int[] 代表每次执行sql的更新条数
public int[] updateOrSaveBatch(String updateOrInsertSql, List<UserPo> userList) {
return jdbcTemplate.batchUpdate(updateOrInsertSql,
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setObject(1, userList.get(i).getId());
ps.setObject(2, userList.get(i).getName());
}
@Override
public int getBatchSize() {
return userList.size();
}
});
}