事务:逻辑上的一组操作,要么都执行,要么都不执行
举个例子:A向B转账1000,那么A的账户需要减少1000,B的账户需要增加1000,在数据库底层这是两个更新操作。在这个过程中,两个更新操作必须全部执行成功或者失败,否则A和B谁也不乐意。
MySQL:
1.支持事务的,默认会自动提交事务。每条语句都在一个单独的事务中
2.手动控制事务:
开启事务:start transaction | begin
提交事务:commit
回滚事务:rollback
JDBC控制事务:
事务的特性:
- 原子性:指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性:事务必须使数据库从一个一致性状态变换到另外一个一致性状态。转账前和转账后的总金额不变。
- 隔离性:事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
- 持久性:指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。
事务的隔离级别:
赃读:指一个事务读取了另一个事务未提交的数据。(A向B转账,A在事务内向B转1000,但是并没有提交事务,此时跟B说我向你转钱了,B一查自己的账户,确实多了1000,跟A确认收到了,但是此时A回滚了,那么B此时再去查账就会发现之前的转账没了)
不可重复读:在一个事务内读取表中的某一行数据,多次读取结果不同。一个事务读取到了另一个事务提交后的数据。(update,A和B事务,如果在B事务中不断的对某一个数据更新,那么在A中只要查询便可读取到)
虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。 (insert,A和B事务,在B事务中插入了数据,那么在事务A中可以读取到)
数据库通过设置事务的隔离级别防止以上情况的发生:
* 1、READ UNCOMMITTED: 赃读、不可重复读、虚读都有可能发生。
* 2、READ COMMITTED: 避免赃读。不可重复读、虚读都有可能发生。(oracle默认的)
* 4、REPEATABLE READ:避免赃读、不可重复读。虚读有可能发生。(mysql默认)
* 8、SERIALIZABLE: 避免赃读、不可重复读、虚读。
级别越高,性能越低,数据越安全
mysql中:
查看当前的事务隔离级别:SELECT @@TX_ISOLATION;
更改当前的事务隔离级别:SET TRANSACTION ISOLATION LEVEL 四个级别之一。
设置隔离级别必须在事务之前
JDBC中:
Connection接口:
设置隔离级别:必须在开启事务之前
//Connection.setTransactionIsolation(int level);
con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
连接池:可以理解为一个存放很多数据库连接的容器。负责分配、管理和释放数据库连接,它允许应用重复使用一个现有的数据库连接而不是重新建立一个;释放空闲时间超过最大空闲时间的数据库连接,从而避免因没有释放数据库连接而引起的数据库连接遗漏,从而提高对数据库操作的性能。
应用程序直接获取数据库连接:
缺点:用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、宕机。
使用连接池进行优化:
下面我们自己编写一个数据库连接池的小程序,但是并没有任何实际开发的意义:
思路:类中添加一个实例域LinkedList作为存放连接池的容器,每次获得连接就从容器中移除该连接,使用结束后关闭连接实际上是释放数据库连接,将使用结束的连接再添加到容器中
使用和释放数据库连接资源遵守先进先出的原则
package com.dream.pool;
import com.dream.utils.DBUtils;
import java.sql.Connection;
import java.util.Collections;
import java.util.LinkedList;
/**
* 模拟连接池,不具有实际开发意义
* Created by Dream on 2017/12/2.
*/
public class SimpleConnectionPool {
//创建一个存放连接的连接池
private static LinkedList<Connection> pools = (LinkedList<Connection>) Collections.synchronizedList(new LinkedList<Connection>());
static {
//静态块中创建10个连接存放在连接池中
try{
for(int i=0;i<10;i++){
//DBUtils是前面博文《JDBC技术》中自定义的工具类
Connection con = DBUtils.getConnection();
pools.add(con);
}
}catch (Exception e){
throw new ExceptionInInitializerError("数据库连接初始化失败,请检查配置文件!");
}
}
//获得连接,从连接池中返回第一个;如果没有,则表示连接池全部被占用,抛出服务器连接繁忙的异常
public Connection getConnection(){
if(pools.size() > 0){
return pools.removeFirst();
}else{
throw new RuntimeException("服务器繁忙...");
}
}
//释放连接
public void release(Connection con){
pools.addLast(con);
}
}
但是,对于自定义的数据库连接池,每个人的定义各不相同,所以为我们提供了统一的接口javax.sql.DataSource接口,称为数据源。其中接口中的方法如下:
需要什么方法,则实现什么方法。对于数据源,我们无非就是获取数据库连接和释放数据库连接。
对于获取数据库连接,就是重写getConnection()方法
对于该接口的实现类,思路跟上述的自定义连接池类似:
1.定义一个LinkedList容器用来存放全部的数据库连接
2.每次获取数据库连接则将该连接从数据库中移除
3.释放数据库连接????
public class MyDataSource implements DataSource {
private static LinkedList<Connection> pools = (LinkedList<Connection>) Collections.synchronizedCollection(new LinkedList<Connection>());
static {
try{
for(int i=0;i<10;i++){
Connection con = DBUtils.getConnection();
pools.add(con);
}
}catch (Exception e){
throw new ExceptionInInitializerError("数据库初始化失败,配置文件有误!");
}
}
@Override
public Connection getConnection() throws SQLException {
Connection con = null;
if(pools.size() > 0){
con = pools.removeFirst();
return con;
}else{
throw new RuntimeException("服务器繁忙...");
}
}
//....
}
对于上述的数据源,有个很大的问题,如何释放已经用完的数据库连接呢?有人可能会觉得很简单,只需要在该接口的实现类中定义release方法就OK了。但是我们应用自定义的数据源的时候,我们是如下使用的:
DataSource ds = new MyDataSource();
Connection con = ds.getConnection();
//由于接口DataBaseSource中并没有release方法,所以并不能通过ds去调用MyDataBaseSource中的release方法,那这该怎么办?
针对上述问题,我们可以通过前面博文中提到的装饰者设计模式来解决这个棘手的问题。
目的:改写已存在的类的某个方法或某些方法。那么对于上述的问题无非就是改写close方法,使得close方法不再是关闭资源,而是释放资源
装饰设计模式(包装模式)口诀:
1、编写一个类,实现与被包装类相同的接口。(具备相同的行为)
2、定义一个被包装类类型的变量。
3、定义构造方法,把被包装类的对象注入,给被包装类变量赋值。
4、对于不需要改写的方法,调用原有的方法。
5、对于需要改写的方法,写自己的代码。
自定义Connection类实现Connection接口:
还有一些方法,也就是Connection接口的方法,都需要实现,通过oldCon调用原有的方法即可。
那么,如何使用呢?在自定义的数据源中使用,使用户在获取数据库连接的时候获取到的就是改写了close方法的自定义连接接口实现类
public class MyDataSource implements DataSource {
private static LinkedList<Connection> pools = (LinkedList<Connection>) Collections.synchronizedCollection(new LinkedList<Connection>());
static {
try{
for(int i=0;i<10;i++){
Connection con = DBUtils.getConnection();
pools.add(con);
}
}catch (Exception e){
throw new ExceptionInInitializerError("数据库初始化失败,配置文件有误!");
}
}
@Override
public Connection getConnection() throws SQLException {
Connection con = null;
if(pools.size() > 0){
con = pools.removeFirst();
//将原来的连接用自定义的连接包装一下,返回包装后的连接对象
Connection myCon = new MyConnection(con,pools);
return myCon;
}else{
throw new RuntimeException("服务器繁忙...");
}
}
//...
}
还有一种模式也可以解决该问题——默认适配器,不过本质上还是装饰者设计模式,是装饰者设计模式的一个变体
上述并没有减少工作量,还是需要把所有的接口方法全部实现
其实,这特别像HttpServlet,其实HttpServlet就是一个适配器,把Servlet接口中的方法和ServletConfig中的方法都实现了,那么在写自定义Servlet的时候只要继承HttpServlet,按需重写想用的方法,不需要的方法则无需出现在自定义的类中了。
下面说一下常见的数据源:
DBCP:Apache推出的Database Connection Pool
使用步骤:
- 添加jar包 commons-dbcp-1.4.jar commons-pool-1.5.6.jar
- 添加属性资源文件
- 编写数据源工具类
package com.dream.utils;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
/**
* Created by Dream on 2017/12/4.
*/
public class DBCPUtils {
private static DataSource ds = null;
static {
Properties pro = new Properties();
try{
pro.load(DBCPUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties")); //获取配置文件资源
ds = BasicDataSourceFactory.createDataSource(pro);
}catch (Exception e){
throw new ExceptionInInitializerError("初始化错误,请检查配置文件!");//在静态初始值或静态变量的初始值期间发生异常。
}
}
public static Connection getConnection(){
try {
return ds.getConnection();
}catch (Exception e){
throw new RuntimeException("创建数据库连接失败!");
}
}
public static void release(ResultSet rs,Statement sta,Connection con){
if(rs != null){
try{
rs.close();
}catch (Exception e){
rs = null;
}
}
if(sta != null) {
try{
sta.close();
}catch (Exception e){
sta = null;
}
}
if(con != null){
try{
con.close(); //直接调用close方法即可。因为框架也已经考虑到了这个问题,至于底层是关闭还是释放取决于该数据库连接的来源
}catch (Exception e){
con = null;
}
}
}
}
C3P0:一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
使用步骤:
- 1、添加jar包
- 2、编写配置文件:c3p0-config.xml,放在classpath中,或classes目录中
配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl" >jdbc:mysql://localhost:3306/daily_dream?useSSL=false</property>
<property name="user" >root</property>
<property name="password" >root</property>
<!--初始化连接池的大小-->
<property name="initialPoolSize" >10</property>
<property name="checkoutTimeout" value="30000" />
<!--最大空闲时间-->
<property name="maxIdleTime" >180</property>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数 -->
<property name="acquireIncrement">5</property>
<!--初始化时获取十个连接,取值应在minPoolSize与maxPoolSize之间 -->
<property name="initialPoolSize">10</property>
<!--连接池中保留的最小连接数 -->
<property name="minPoolSize">10</property>
<!--连接池中保留的最大连接数 -->
<property name="maxPoolSize">50</property>
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。
但由于预缓存的statements属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。
如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0-->
<property name="maxStatements">20</property>
</default-config>
</c3p0-config>
C3P0Utils:
package com.dream.utils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Created by Dream on 2017/12/4.
*/
public class C3P0Utils {
private static DataSource ds = new ComboPooledDataSource();
public static Connection getConnection(){
try {
return ds.getConnection();
} catch (SQLException e) {
throw new RuntimeException("服务器错误......");
}
}
public static void release(ResultSet rs, Statement sta, Connection con){
if(rs != null){
try{
rs.close();
}catch (Exception e){
rs = null;
}
}
if(sta != null) {
try{
sta.close();
}catch (Exception e){
sta = null;
}
}
if(con != null){
try{
con.close(); //直接调用close方法即可。因为框架也已经考虑到了这个问题,至于底层是关闭还是释放取决于该数据库连接的来源
}catch (Exception e){
con = null;
}
}
}
}
用JavaWeb服务器管理数据源:Tomcat,其中有内部数据源
Tomcat:(DBCP)数据源只需要配置服务器即可。
配置数据源的步骤:
- 1、拷贝数据库连接的jar到tomcatlib目录下
2、配置数据源XML文件
a)如果把配置信息写在tomcat下的conf目录的context.xml中,那么所有应用都能使用此数据源。 b)如果是在当前应用的META-INF中创建context.xml, 编写数据源,那么只有当前应用可以使用。
META-INF/context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/ConnectionPool" auth="Container"
type="javax.sql.DataSource" username="root" password="root"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/daily_dream?useSSL=false"
maxActive="8" maxIdle="4"/>
</Context>
其中name自定义任何值均可,但是一般是jdbc/项目名称
对于服务器配置的数据源必须通过用户请求
path固定,name则是配置文件中的name值
JNDI:java nameing directory interface
JNDI容器就是一个Map
对于服务器内部的数据源,我们也可以直接在服务器的配置文件(tomcat下的conf目录的)context.xml进行配置,而且可以配置多个数据源(MySQL,Oracle,SQLServer),通过path+name的键值去找数据源对象即value