目录
一、装饰设计模式
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的接口,对所有方法进行重写,毫无疑问这种办法效率很低下。
面对这种只需要改写类中个别方法,而大部分方法可以直接使用的情况,我们就要用到装饰者模式了,该模式设计步骤为:
- 编写一个类,实现与被包装类(Monkey类)相同的接口(Creature接口)
- 定义一个被包装类类型的变量(Monkey类型的变量)
- 定义构造方法,把被包装类的对象(Monkey类对象)注入,给被包装类变量赋值(Monkey类型的变量)
- 对于不需要改写的方法,调用原有的方法(Monkey类的方法)
- 对于需要改写的方法,写自己的代码(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适配器模式
装饰者模式的好处显而易见,但是也有个很明显的缺点,就是接口有多少种方法,你就要一个个地写多少种,虽然说不需要实现,只用调用被包装类对象的对应方法,但是该过程还是比较繁琐的。
适配器模式就解决了这个问题,该模式仅专注于需要修改的方法,不需要对不更改的方法进行重写,适配器模式步骤如下:
- 编写一个类,继承包装类适配器
- 定义一个被包装类类型的变量
- 定义构造方法,把被包装类的对象注入,给被包装类变量赋值
- 对于所有方法,调用被包装类原有的方法
这样就创建好了一个类的适配器,相当于把原来的类复制了一遍,然后我们新建一个类,直接继承适配器,这样就不用实现所有的方法,只需要重写个别方法即可。
二、事务
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、虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致
数据库通过设置事务的隔离级别防止以上情况的发生:
- READ UNCOMMITTED:赃读、不可重复读、虚读都有可能发生
- READ COMMITTED:避免赃读,但是不可重复读、虚读有可能发生(oracle默认设置)
- REPEATABLE READ:避免赃读、不可重复读,但是虚读有可能发生(mysql默认设置)
- 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接口,并完成连接池功能步骤如下:
- 在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中
- 实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户
- 当用户使用完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的步骤如下:
我们首先创建一个新的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的步骤如下:
- 添加jar包(提取码mcoe)
- 编写配置文件,放置在classes目录中
- 编写工具类
还要添加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公司研发的,下面我们看看如何使用服务器配置数据源:
- 拷贝数据库连接jar包(即mysql-connector-java-5.0.8-bin.jar)到tomcat的lib目录下
- 配置数据源XML文件
- 方式一:如果把配置信息写在tomcat下的conf目录的context.xml中,那么所有应用都能使用此数据源
- 方式二:如果是在当前应用的META-INF中创建context.xml, 编写数据源,那么只有当前应用可以使用
- 使用连接池
首先我们拷贝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>