JavaWeb——(12)事务与连接池

目录

 一、装饰设计模式

1.1装饰设计模式

1.2适配器模式

二、事务

2.1事务的概述

2.2事务的特性

2.3事务的隔离级别

2.3.1隔离级别概述

2.3.2JDBC设置事务隔离级别

三、连接池

3.1连接池概述

3.2连接池模拟

3.3常用的数据源配置

3.3.1DBCP

3.3.2C3P0

四、使用JavaWeb服务器(Tomcat)管理数据源

4.1Tomcat管理数据源

4.2JNDI


 一、装饰设计模式

1.1装饰设计模式

装饰设计模式的主要目的是修改已存在类的功能,假设有这么一个接口Creature,它定义了很多生物的一些方法,

public interface Creature {
    public abstract void sleep();
    public abstract void walk();
    public abstract void eat();
    public abstract void breath();
}

这个时候我们定义了一个灵长猿猴的类,实现Creature的接口,

public class Monkey implements Creature{
    @Override
    public void sleep() {
        System.out.println("躺着休息");
    }
    @Override
    public void walk() {
        System.out.println("四只脚走路");
    }
    @Override
    public void eat() {
        System.out.println("素食荤食都吃");
    }
    @Override
    public void breath() {
        System.out.println("呼吸氧气");
    }
}

如果我们此时想再定义人的类,人和灵长猿猴其他方法差不多,主要是行走的方式不同,如果我们再实现Creature的接口,对所有方法进行重写,毫无疑问这种办法效率很低下。

面对这种只需要改写类中个别方法,而大部分方法可以直接使用的情况,我们就要用到装饰者模式了,该模式设计步骤为:

  1. 编写一个类,实现与被包装类(Monkey类)相同的接口(Creature接口)
  2. 定义一个被包装类类型的变量(Monkey类型的变量)
  3. 定义构造方法,把被包装类的对象(Monkey类对象)注入,给被包装类变量赋值(Monkey类型的变量)
  4. 对于不需要改写的方法,调用原有的方法(Monkey类的方法)
  5. 对于需要改写的方法,写自己的代码(Human类的方法)

下面我们创建一个Human类,

public class Human implements Creature{
    private Monkey monkey;
    public Human(Monkey monkey){
        this.monkey=monkey;
    }
    @Override
    public void sleep() {
        monkey.sleep();
    }
    @Override
    public void walk() {
        System.out.println("双脚独立行走");
    }
    @Override
    public void eat() {
        monkey.eat();
    }
    @Override
    public void breath() {
        monkey.breath();
    }
}

可以看到我们仅仅重写了walk方法,其他方法都是调用传进来的Monkey对象的方法,这种设计模式称为装饰设计模式。

1.2适配器模式

装饰者模式的好处显而易见,但是也有个很明显的缺点,就是接口有多少种方法,你就要一个个地写多少种,虽然说不需要实现,只用调用被包装类对象的对应方法,但是该过程还是比较繁琐的。

适配器模式就解决了这个问题,该模式仅专注于需要修改的方法,不需要对不更改的方法进行重写,适配器模式步骤如下:

  1. 编写一个类,继承包装类适配器
  2. 定义一个被包装类类型的变量
  3. 定义构造方法,把被包装类的对象注入,给被包装类变量赋值
  4. 对于所有方法,调用被包装类原有的方法

这样就创建好了一个类的适配器,相当于把原来的类复制了一遍,然后我们新建一个类,直接继承适配器,这样就不用实现所有的方法,只需要重写个别方法即可。

二、事务

2.1事务的概述

事务是指访问并可能更新数据库中数据项的执行单元,可以是一条sql语句、一组sql语句或者整个程序,

组成这组操作的各个单元,要么全部成功,要么全部失败。事务是恢复和并发控制的基本单位。

例如假设用户a要向用户b转账100块钱,后台数据库更新会对应两条sql语句:

update account set money=money-100 where name='a';
update account set money=money+100 where name='b';

如果此时第一条语句更新失败,那么第二条语句理论来说也不应该更新成功,这就是事务的原子性。

数据库操作事务的命令DTL有以下几个:

  • start transaction:开启事务
  • Rollback:回滚事务
  • Commit:提交事务

在mysql中是默认自动提交事务的,每条语句都处在单独的事务中。

我们使用代码模拟事务的原子性,首先在mysql中创建account表,有name和money两个属性,然后连接数据库进行money的更新。

package Transaction;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class TransactionTest {
    @Test
    public void test1(){
        Connection connection=null;
        PreparedStatement ps=null;

        try {
            connection=DBUtils.getConnection();//获取数据库连接
            connection.setAutoCommit(false);//设置事务不自动提交,相当于begin开启事务
            ps=connection.prepareStatement("update account set money=money-500 where name='aaa'");
            ps.executeUpdate();//执行sql更新语句

            int i=10/0;//此处为异常,模拟程序中断

            ps=connection.prepareStatement("update account set money=money+500 where name='bbb'");
            ps.executeUpdate();//执行sql更新语句
            connection.commit();//提交事务

        } catch (Exception e) {
            try {
                connection.rollback();//如果发生了异常就回滚事务,保持事务的原子性
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            DBUtils.closeAll(null,ps,connection);//关闭数据库连接
        }

    }

}

2.2事务的特性

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性

  • 原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
  • 一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
  • 隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

2.3事务的隔离级别

2.3.1隔离级别概述

事务隔离级别的不同,也会导致数据不同的影响,如果隔离的级别过低,那么会造成很多错误的数据读取方式,如果隔离的级别过高,读取性能则会有所下降。

在低级别的事务隔离情况下,主要有以下几种错误的数据读取方式:

1、脏读:指一个事务读取了另一个事务没有提交的数据

2、不可重复读:在一个事务内读取表中的某一行数据,多次读取结果不同(事务A读取到了事务B提交后的数据)

3、虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致

数据库通过设置事务的隔离级别防止以上情况的发生:

  1. READ UNCOMMITTED:赃读、不可重复读、虚读都有可能发生
  2. READ COMMITTED:避免赃读,但是不可重复读、虚读有可能发生(oracle默认设置)
  3. REPEATABLE READ:避免赃读、不可重复读,但是虚读有可能发生(mysql默认设置)
  4. SERIALIZABLE:避免赃读、不可重复读、虚读

级别越高,性能越低,但是数据越安全。

在mysql中有关事务的命令有

SELECT @@TX_ISOLATION;-- 查看当前的事务隔离级别
SET TRANSACTION ISOLATION LEVEL 四个级别之一;-- 更改当前的事务隔离级别

需要注意,设置事务的隔离级别需要在事务开始之前。
 

2.3.2JDBC设置事务隔离级别

在JDBC中,事务的隔离级别关键字封装在了Connection对象中,

public static final int TRANSACTION_NONE 0 //指示不支持事务的常量
public static final int TRANSACTION_READ_UNCOMMITTED 1 //可能会发生脏读,不可重复读和幻读
public static final int TRANSACTION_READ_COMMITTED 2 //防止脏读; 可能会发生不可重复的读取和幻读
public static final int TRANSACTION_REPEATABLE_READ 4 //防止了脏读和不可重复读; 可以发生幻读
public static final int TRANSACTION_SERIALIZABLE 8 //防止脏读,不可重复读和幻读

修改事务隔离级别语句如下:

Connection con=null;
con=DBUtils.getConnection();//获取数据库连接
con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);//设置事务隔离级别为防止脏读、不可重复读

三、连接池

3.1连接池概述

数据库连接池是负责分配、管理和释放数据库连接的,它允许应用程序重复使用一个现有的数据库连接对象,而不是再重新建立一个。

数据库创建连接通常需要消耗较大的资源,创建时间也比较长,如果用户每次请求都需要向服务器获得连接,那么当访问量大时创建连接就要消耗不少时间,极大的浪费了数据库的资源,极易造成服务器内存溢出与宕机。

而数据库连接池是预先定义好一定容量的连接池,里面包含了一定数量的数据库连接,它可以动态的分配连接池,并且允许多个应用程序重复使用一个连接对象,而不是为每一个请求都建立新的数据库连接,这种技术极大的提高了数据库的使用效率与性能。

3.2连接池模拟

编写连接池时需要实现官方的javax.sql.DataSource接口,其中DataSource中定义了两个重载的getConnection()方法,在实现接口时要重写这两个方法。

实现DataSource接口,并完成连接池功能步骤如下:

  1. 在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中
  2. 实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户
  3. 当用户使用完Connection,调用Connection.close()方法时,Collection对象应保证将自己返回到LinkedList中,而不是把con还给数据库

需要注意的是,Connection.close()方法是关闭数据库连接,但是这里我们想要将连接返回连接池,而不是关闭,所以我们需要改写close方法,这里就要用到装饰设计模式,我们首先对Connection进行包装,改写其中的close方法,

import java.sql.*;
import java.util.*;

public class MyConnection implements Connection {
    private Connection oldConnection;//原java.sql.jdbc.Connection对象
    private LinkedList<Connection> pool;//连接池

    public MyConnection(Connection oldConnection, LinkedList<Connection> pool){
        this.oldConnection=oldConnection;//获取java.sql.jdbc.Connection对象
        this.pool=pool;//获取连接池对象
    }

    @Override
    //对close方法进行重写
    public void close() throws SQLException {
        pool.addLast(oldConnection);
    }

    @Override
    //其他方法调用原java.sql.jdbc.Connection对象的方法即可
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return oldConnection.prepareStatement(sql);
    }

    ...
}

然后实现DataSource接口,完成连接池功能,

import Transaction.DBUtils;

import java.io.PrintWriter;
import java.sql.*;
import java.util.*;

public class MyDataSource implements DataSource {

    //创建一个存放连接的连接池
    private static LinkedList<Connection> pool=(LinkedList<Connection>) Collections.synchronizedList(new LinkedList<Connection>());

    static{
        try {
            for (int i = 0; i < 10; i++) {
                MyConnection connection=new MyConnection(DBUtils.getConnection(),pool);
                pool.add(connection);
            }
        } catch (Exception e) {
            throw new ExceptionInInitializerError("初始化数据库连接失败");
        }
    }

    @Override
    //从连接池中获取数据库连接
    public Connection getConnection() throws SQLException {
        Connection con=null;
        if (pool.size()>0){
            con=pool.removeFirst();//返回第一个连接,并移除
            Connection myCon=new MyConnection(con,pool);//返回包装后的MyConnection对象
            return myCon;
        }else{
            throw new RuntimeException("服务器忙");
        }
    }

    ...
}

3.3常用的数据源配置

我们还可以使用已经在包中写好的连接池直接实现,这里我们介绍一些常用的数据源(连接池)

3.3.1DBCP

DBCP全称为Database Connection Pool,是Apache公司推出的数据库连接池,使用DBCP的步骤如下:

  1. 添加jar包(提取码5adx)
  2. 添加属性资源文件(提取码97vo)
  3. 编写数据源工具类

我们首先创建一个新的Web项目,将jar包放入到WEB-INF文件夹下的lib路径下,然后添加属性资源文件,注意修改连接设置中的数据库名还有用户名和密码,

然后新建一个DBCP的工具类,用于实现获取连接池中的连接,以及释放资源,

import org.apache.commons.dbcp.BasicDataSourceFactory;

import javax.sql.*;
import java.util.Properties;

public class DBCPUtil {
    private static DataSource ds=null;
    static{
        Properties prop=new Properties();
        try {
            prop.load(DBCPUtil.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"));//根据DBCPUtil类的路径,加载配置
            ds=BasicDataSourceFactory.createDataSource(prop);//根据配置文件创建数据源(连接池)
        } catch (Exception e) {
            throw new ExceptionInInitializerError("初始化错误,请检查配置文件");
        }
    }

    public static Connection getConnection(){
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException("服务器忙");
        }
    }

    public static void release(Connection connection, Statement statement, ResultSet resultSet){
        if(resultSet!=null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            resultSet=null;
        }
        if(statement!=null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            statement=null;
        }
        if(connection!=null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            connection=null;
        }
    }

}

3.3.2C3P0

C3P0是一个开源的JDBC连接池,它和DBCP的主要区别在于:

  • DBCP没有自动回收空闲连接的功能
  • C3P0有自动回收空闲连接功能

使用C3P0的步骤如下:

  1. 添加jar包(提取码mcoe)
  2. 编写配置文件,放置在classes目录中
  3. 编写工具类

还要添加mysql的连接的jar包(提取码2j98),然后我们编写C3P0的工具类

import com.mchange.v2.c3p0.*;

import javax.sql.*;

public class C3P0Util {
    private static DataSource dataSource = new ComboPooledDataSource();

    public static Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException("服务器错误");
        }
    }

    public static void release(Connection connection, Statement statement, ResultSet resultSet){
        if(resultSet!=null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            resultSet=null;
        }
        if(statement!=null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            statement=null;
        }
        if(connection!=null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            connection=null;
        }
    }
}

添加以下配置文件c3p0-config.xml,放在classes目录下或者源路径下:

<c3p0-config>
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
        <property name="user">root</property>
        <property name="password">3837</property>

        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
        <property name="maxStatements">200</property>

        <user-overrides user="test-user">
            <property name="maxPoolSize">10</property>
            <property name="minPoolSize">1</property>
            <property name="maxStatements">0</property>
        </user-overrides>

    </default-config>
</c3p0-config>

然后测试一下,

import java.sql.*;

public class JDBCTest {
    public static void main(String[] args) {
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        try{
            connection=C3P0Util.getConnection();//从连接池中获取数据库连接
            statement=connection.prepareStatement("SELECT * FROM user");
            resultSet=statement.executeQuery();

            while(resultSet.next()){
                System.out.print(resultSet.getString("username")+"  ");
                System.out.print(resultSet.getString("password")+"  ");
                System.out.print(resultSet.getString("entry_date")+"  ");
                System.out.println();
            }
        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            DBCPUtil.release(connection,statement,resultSet);
        }
    }
}

结果如下:

四、使用JavaWeb服务器(Tomcat)管理数据源

4.1Tomcat管理数据源

在开发JavaWeb应用时,必须要使用JavaWeb服务器,而服务器基本都内置数据源,像Tomcat服务器内置的就是DBCP数据源,因为这俩都是Apche公司研发的,下面我们看看如何使用服务器配置数据源:

  1. 拷贝数据库连接jar包(即mysql-connector-java-5.0.8-bin.jar)到tomcat的lib目录下
  2. 配置数据源XML文件
    1. 方式一:如果把配置信息写在tomcat下的conf目录的context.xml中,那么所有应用都能使用此数据源
    2. 方式二:如果是在当前应用的META-INF中创建context.xml, 编写数据源,那么只有当前应用可以使用
  3. 使用连接池

首先我们拷贝jar包到lib目录下之后,配置数据源XML文件,这里我们使用第二种方式,

<Context>
    <Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
              maxActive="100" maxIdle="30" maxWait="10000"
              username="root" password="3837" driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/mydb1"/>
</Context>

然后在web目录下的index.jsp中使用java语句进行测试连接,

<%@ page import="javax.naming.Context" %>
<%@ page import="javax.sql.DataSource" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="javax.naming.InitialContext" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
    <%
      Context initContext = new InitialContext();
      DataSource ds = (DataSource)initContext.lookup("java:/comp/env/jdbc/TestDB");
      Connection conn = ds.getConnection();
      out.print(conn);
    %>
  </body>
</html>

启动服务器,显示如图:

我们还可以在tomcat下的conf目录的context.xml中配置多个数据源,通过不同的名字进行调用。

4.2JNDI

JNDI全称为:java nameing directory interface,就是相当于一个map集合。

比如上面我们在tomcat的conf目录的context.xml中配置多个数据源,这些Resource就构成了JNDI,我们直接在Context的lookup方法里通过name属性找到我们需要的数据源。

<Context>
    <!--mysql数据源-->
    <Resource name="jdbc/mysql" auth="Container" type="javax.sql.DataSource"
              maxActive="100" maxIdle="30" maxWait="10000"
              username="root" password="3837" driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/mydb1"/>

    <!--mysql数据源-->
    <Resource name="jdbc/oracle" auth="Container" type="javax.sql.DataSource"
              maxActive="100" maxIdle="30" maxWait="10000"
              username="root" password="3837" driverClassName="com.oracle.jdbc.Driver"
              url="jdbc:oracle://localhost:3306/mydb1"/>

    <!--mysql数据源-->
    <Resource name="jdbc/sqlserver" auth="Container" type="javax.sql.DataSource"
              maxActive="100" maxIdle="30" maxWait="10000"
              username="root" password="3837" driverClassName="com.sqlserver.jdbc.Driver"
              url="jdbc:sqlserver://localhost:3306/mydb1"/>
    ...
</Context>

猜你喜欢

转载自blog.csdn.net/weixin_39478524/article/details/117923222