返回顶部

JDBC连接池basedao

JDBC核心技术


一、对资源的讨论

connection是一种稀有资源,一个连接建立就是创造了一个资源,我们思考一个问题,一个QQ连上了服务器对服务器而言就是建立了一个连接,这是有代价的。我们常常听说,同时在线人数太多,会导致服务崩溃,就是这么个道理。

那通常我们有什么解决方案呢?

第一种方案:就一个人玩就行了,我就是全服第一。

第二种方案:将服务器的人数限定一下,最多不能超过多少,超过了就排队,这当然不错,对吧。

第一种方案:单例

单例模式解决连接问题:

private static Connection connection = null;

public static Connection getConnection(){
    //调用方法时,如果发现没有初始化,就创建一个
    if(DBUtil.connection == null){
        String url = "jdbc:mysql://localhost:3306/xinzhishop";
        String user = "root";
        String password = "root";
        String driverName = "com.mysql.jdbc.Driver";

        try {
            DBUtil.connection = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    return DBUtil.connection;
}
private static Connection connection = null;

//只要类一家在咱们就搞一个连接
static {
    String url = "jdbc:mysql://localhost:3306/xinzhishop";
    String user = "root";
    String password = "root";
    String driverName = "com.mysql.jdbc.Driver";

    try {
        DBUtil.connection = DriverManager.getConnection(url, user, password);
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

public static Connection getConnection(){
    return DBUtil.connection;
}

单例只是一种思想;

1、connection只从一个地方获取

2、实例化的时间定义他是懒汉式还是恶汉式

3、懒汉式:就是不到关键时候就不动,用的时候才创建对象

4、饿汉式:就是二话不说类一加载就创建对象

通常的单例类的写法

public class Dog {
    //初始化一个空对象
    private static Dog dog = null;
    //私有化构造方法,让别的地方不能new
    private Dog(){}
    //获取实例的方法
    public Dog getInstance(){
        //看看是不是null,是null,再new一个
        if( dog == null ){
            Dog.dog = new Dog();
        }
        return dog;
    }
    
}
/**
 * @author zn
 * @date 2020/4/3
 **/
public class Dog {
	//静态的变量一加载就初始化
    private static Dog dog = new Dog();
    //私有化构造方法,让别的地方不能new
    private Dog(){}
    //只能通过这个静态方法获取实例
    public static Dog getInstance(){
        return dog;
    }

}

第二种方案:数据库连接池

1、 JDBC数据库连接池的必要性

  • 在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:  

    • 在主程序中建立数据库连接
    • 进行sql操作
    • 断开数据库连接
  • 这种模式开发,存在的问题:

    • 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
    • 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
    • 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

2、 数据库连接池技术

  • 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。

  • 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。

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

  • 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

  • 数据库连接池技术的优点

    1. 资源重用

    由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。

    2. 更快的系统反应速度

    数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间

    3. 新的资源分配手段

    对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源

    4. 统一的连接管理,避免数据库连接泄漏

    在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露

3、 多种开源的数据库连接池

  • DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
  • DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
  • 特别注意:
    • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
    • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。

(1) C3P0数据库连接池(太老了,不学)

(2) DBCP数据库连接池(太老了,不学)

(3) Druid(德鲁伊)数据库连接池

引入一个jar包

druid-1.1.17.jar

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

package com.atguigu.druid;

import java.sql.Connection;
import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

public class TestDruid {
	public static void main(String[] args) throws Exception {
		Properties pro = new Properties();		 pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
		DataSource ds = DruidDataSourceFactory.createDataSource(pro);
		Connection conn = ds.getConnection();
		System.out.println(conn);
	}
}

配置文件为:【druid.properties】

url=jdbc:mysql://localhost:3306/xinzhishop?rewriteBatchedStatements=true
username=root
password=root
driverClassName=com.mysql.jdbc.Driver

initialSize=10
maxActive=20
maxWait=1000
filters=wall
    
    
1、初始化时建立物理连接的个数 默认0
2、最大连接池数量  默认8
3、获取连接时最大等待时间,单位毫秒。
4、防御sql注入的filter:wall

(4) Hikari(光)数据库连接池

引入四个jarbao:

HikariCP-3.4.2.jar
slf4j-api-1.7.29.jar
slf4j-log4j12-1.7.21.jar
log4j-1.2.17.jar

号称历史上最快的数据库连接池

public static Connection getConnection(){
    //1.数据库连接的4个基本要素:
    Connection connection = null;
    InputStream in = UserDaoImpl.class.getClassLoader().getResourceAsStream("config/jdbc.config");
    Properties properties = new Properties();
    try {
        properties.load(in);
    } catch (IOException e) {
        e.printStackTrace();
    }

    HikariConfig hikariConfig = new HikariConfig(properties);
    DataSource source = new HikariDataSource(hikariConfig);
    try {
        connection = source.getConnection();
    } catch (SQLException e) {
        e.printStackTrace();
    }

    return connection;
}
jdbcUrl=jdbc:mysql://localhost:3306/xinzhishop
username=root
password=root
driverClassName=com.mysql.jdbc.Driver

idleTimeout=600000
connectionTimeout=30000
minimumIdle=10
maximumPoolSize=60

1、保持连接的最大时长,比如连接多了,最小连接数不够用,就会继续创建,比如又创建了10个,如果这时没有了业务,超过该设置的时间,新创建的就会被关闭
2、连接的超时时间,比如网络不好,一个连接超过折磨常时间没有创建好就不创建了
3、连接池最少的连接数
4、连接池最大的连接数

看见红色的日志难受

需要在src下建立一个log4j.properties文件内容,这个文件告诉我们启动连接池的一些日志信息

log4j.rootLogger=debug, stdout, R

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%5p - %m%n

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=firestorm.log

log4j.appender.R.MaxFileSize=100KB
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

log4j.logger.com.codefutures=DEBUG

二、搞一个通用DAO

  • DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。

  • 在所有的dao中会有很多重复性的工作,我们可以封装一个父类来完成此类重复工作,我们称之为BaseDAO。

1、入门级basedao

/**
 * @author zn
 * @date 2020/4/4
 **/
public class BaseDaoImpl implements IBaseDao {

    private static DataSource DATA_SOURCE = null;

    static {
        InputStream in = UserDaoImpl.class.getClassLoader().getResourceAsStream("config/jdbc.config");
        Properties properties = new Properties();
        try {
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }

        HikariConfig hikariConfig = new HikariConfig(properties);
        BaseDaoImpl.DATA_SOURCE = new HikariDataSource(hikariConfig);
    }

    public Connection getConnection() {
        if (BaseDaoImpl.DATA_SOURCE != null) {
            try {
                return BaseDaoImpl.DATA_SOURCE.getConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

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

    public void save(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement statement = null;
        try {
            conn = getConnection();
            statement = conn.prepareStatement(sql);
            for (int i = 1; i <= params.length; i++) {
                statement.setObject(i, params[i]);
            }
            statement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeAll(statement, null);
        }
    }
}

2、大神级basedao

终极目标,少部分人能看懂,会用即可

package com.xinzhi.dao;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.List;

/**
 * @author zn
 * @date 2020/4/4
 **/
public interface IBaseDao<T> {

    /**
     * 获取连接的方法
     * @return
     */
    Connection getConnection();

    /**
     * 关闭资源的方法
     * @param statement
     * @param resultSet
     */
    void closeAll(Statement statement, ResultSet resultSet);

    /**
     * 通用的保存方法
     * @param sql
     * @param params
     */
    void save(String sql,Object... params);

    /**
     * 高级部分,大神写的
     * 有要求 数据库的名字和类名必须一样
     *        每个字段和属性的名字也要一样
     * 有规矩好办事
     */


    /**
     * 通用的查询所有的结果的方法
     * @param clazz
     * @return
     */
    List<T> findAll(Class clazz);

    /**
     * 通用保存的方法
     * @param obj
     */
    void save(Object obj);

    /**
     * 通用的更新方法
     * @param obj
     * @param fieldName
     * @param fieldValue
     */
    void update(Object obj,String fieldName,Object fieldValue);

    /**
     * 通用的删除方法
     * @param clazz
     * @param fieldName
     * @param fieldValue
     */
    void delete(Class clazz,String fieldName,Object fieldValue);

    /**
     * 通用的查找一个方法
     * @param clazz
     * @param fieldName
     * @param fieldValue
     * @return
     */
    T findOne(Class clazz,String fieldName,Object fieldValue);
}

package com.xinzhi.dao.impl;

import com.xinzhi.dao.IBaseDao;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @author zn
 * @date 2020/4/4
 **/
public class BaseDaoImpl<T> implements IBaseDao<T> {

    private static DataSource DATA_SOURCE = null;

    static {
        InputStream in = UserDaoImpl.class.getClassLoader().getResourceAsStream("config/jdbc.config");
        Properties properties = new Properties();
        try {
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }

        HikariConfig hikariConfig = new HikariConfig(properties);
        BaseDaoImpl.DATA_SOURCE = new HikariDataSource(hikariConfig);
    }

    public Connection getConnection() {
        if (BaseDaoImpl.DATA_SOURCE != null) {
            try {
                return BaseDaoImpl.DATA_SOURCE.getConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

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

    //简单的通用保存,通过可变参数赋值
    public void save(String sql, Object... params) {
        PreparedStatement statement = null;
        try {
            Connection conn = getConnection();
            statement = conn.prepareStatement(sql);
            for (int i = 1; i <= params.length; i++) {
                statement.setObject(i, params[i]);
            }
            statement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeAll(statement, null);
        }
    }

    /**
     * 高级部分
     * 有要求 数据库的名字和类名必须一样
     * 每个字段和属性的名字也要一样
     * 有规矩好办事,重在体会思想
     * 搞明白还能这么干就行了
     * 思路:
     * 因为规定了数据库名称和类名形同,字段也相同
     * 所有可以通过反射获取类名和字段名拼接一个字符串
     *
     * @return
     */


    public List<T> findAll(Class clazz) {
        //拼一个sql   select id,username,password from user
        //其中id,username,password可变但是他是类的字段啊
        //user可变但是他是类名啊,反射登场了
        List<T> list = new ArrayList<>();
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //利用反射拼出一个select语句
            Field[] fields = clazz.getDeclaredFields();
            StringBuilder fieldStr = new StringBuilder();
            fieldStr.append("select ");
            for (Field field : fields) {
                fieldStr.append(field.getName().toLowerCase()).append(",");
            }
            fieldStr.deleteCharAt(fieldStr.length() - 1);
            fieldStr.append(" from ");
            fieldStr.append(clazz.getName().toLowerCase().substring(clazz.getName().lastIndexOf(".") + 1));

            Connection conn = getConnection();
            statement = conn.prepareStatement(fieldStr.toString());
            resultSet = statement.executeQuery();

            while (resultSet.next()) {
                Object obj = clazz.newInstance();
                for (Field field : fields) {
                    Object value = resultSet.getObject(field.getName());
                    field.setAccessible(true);
                    field.set(obj, value);
                }
                list.add((T) obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeAll(statement, resultSet);
        }
        return list;
    }


    public void save(Object obj) {
        Class clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();

        //拼接出一个insert语句
        StringBuilder sql = new StringBuilder("insert into ");
        sql.append(clazz.getName().toLowerCase().substring(clazz.getName().lastIndexOf(".") + 1))
                .append(" (");
        for (Field field : fields) {
            sql.append(field.getName().toLowerCase()).append(",");
        }
        sql.deleteCharAt(sql.length() - 1);
        sql.append(") values (");
        for (Field field : fields) {
            sql.append("?,");
        }
        sql.deleteCharAt(sql.length() - 1);
        sql.append(")");
        System.out.println(sql);

        PreparedStatement statement = null;
        try {
            Connection conn = getConnection();
            statement = conn.prepareStatement(sql.toString());
            for (int i = 0; i < fields.length; i++) {
                fields[i].setAccessible(true);
                statement.setObject(i + 1, fields[i].get(obj));
            }

            statement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeAll(statement, null);
        }
    }


    @Override
    public void update(Object obj, String fieldName, Object fieldValue) {
        PreparedStatement statement = null;
        try {
            Class clazz = obj.getClass();

            //拼接出一个update语句
            StringBuilder sql = new StringBuilder("update " + clazz.getName().toLowerCase().substring(clazz.getName().lastIndexOf(".") + 1)
                    + " set ");
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                sql.append(field.getName()).append("=").append("?").append(",");
            }
            sql.deleteCharAt(sql.length() - 1);
            sql.append(" where ").append(fieldName).append("=?");
            System.out.println(sql);

            Connection conn = getConnection();
            statement = conn.prepareStatement(sql.toString());
            for (int i = 0; i < fields.length; i++) {
                fields[i].setAccessible(true);
                statement.setObject(i + 1, fields[i].get(obj));
            }
            statement.setObject(fields.length + 1, fieldValue);
            statement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeAll(statement, null);
        }
    }

    @Override
    public void delete(Class clazz, String fieldName, Object fieldValue) {
        //拼接一个delete语句
        String sql = "delete from " + clazz.getName().toLowerCase().substring(clazz.getName().lastIndexOf(".") + 1)
                + " where " + fieldName + "=?";
        System.out.println(sql);
        PreparedStatement statement = null;
        try {
            Connection conn = getConnection();
            statement = conn.prepareStatement(sql);
            statement.setObject(1, fieldValue);
            statement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeAll(statement, null);
        }
    }

    @Override
    public T findOne(Class clazz, String fieldName, Object fieldValue) {
        T t = null;

        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            Field[] fields = clazz.getDeclaredFields();
            //拼接一个语句
            StringBuilder sql = new StringBuilder();
            sql.append("select ");
            for (Field field : fields) {
                sql.append(field.getName().toLowerCase()).append(",");
            }
            sql.deleteCharAt(sql.length() - 1);
            sql.append(" from ");
            sql.append(clazz.getName().toLowerCase().substring(clazz.getName().lastIndexOf(".") + 1))
                    .append(" where " + fieldName + "=?");

            System.out.println(sql.toString());

            Connection conn = getConnection();
            statement = conn.prepareStatement(sql.toString());
            statement.setObject(1,fieldValue);
            resultSet = statement.executeQuery();

            while (resultSet.next()) {
                Object obj = clazz.newInstance();
                for (Field field : fields) {
                    Object value = resultSet.getObject(field.getName());
                    field.setAccessible(true);
                    field.set(obj, value);
                }
                t = (T) obj;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeAll(statement, resultSet);
        }
        return t;
    }
}

三、数据库事务

1、JDBC事务处理

  • 数据一旦提交,就不可回滚。

  • 数据什么时候意味着提交?

    • 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
    • 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。
  • JDBC程序中为了让多个 SQL 语句作为一个事务执行:

    • 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
    • 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
    • 在出现异常时,调用 rollback(); 方法回滚事务

    若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。

【案例:用户AA向用户BB转账100】

public void testJDBCTransaction() {
	Connection conn = null;
	try {
		// 1.获取数据库连接
		conn = JDBCUtils.getConnection();
		// 2.开启事务
		conn.setAutoCommit(false);
		// 3.进行数据库操作
		String sql1 = "update user set balance = balance - 100 where id = ?";
		update(conn, sql1, "AA");

		// 模拟网络异常
		//System.out.println(10 / 0);

		String sql2 = "update user set balance = balance + 100 where id = ?";
		update(conn, sql2, "BB");
		// 4.若没有异常,则提交事务
		conn.commit();
	} catch (Exception e) {
		e.printStackTrace();
		// 5.若有异常,则回滚事务
		try {
			conn.rollback();
		} catch (SQLException e1) {
			e1.printStackTrace();
		}
    } finally {
        try {
			//6.恢复每次DML操作的自动提交功能
			conn.setAutoCommit(true);
		} catch (SQLException e) {
			e.printStackTrace();
		}
        //7.关闭连接
		JDBCUtils.closeResource(conn, null, null); 
    }  
}

2、代码优化

将获取连接放在service层,到层传递,因为事务都在service层。

posted @ 2020-09-05 18:05  Shamero  阅读(222)  评论(0)    收藏  举报