一、工具类的封装
1、JDBC工具类封装:
public class JDBCUtil {
/**
* 获取数据库连接
*
* @return
* @throws Exception
*/
public static Connection getConnection() throws Exception {
// 加载配置文件:获取数据库配置信息
Properties pros = new Properties();
pros.load(JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties"));
String driverClass = pros.getProperty("driverClass");
String url = pros.getProperty("url");
String user = pros.getProperty("user");
String password = pros.getProperty("password");
// 加载驱动
Class.forName(driverClass);
// 获取连接
Connection conn = (Connection) DriverManager.getConnection(url, user, password);
return conn;
}
/**
* 关闭数据库资源
*
* @param rs
* @param ps
* @param conn
*/
public static void close(ResultSet rs, PreparedStatement ps, Connection conn) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 增删改操作
*
* @param sql
* @param args
* @return
* @throws Exception
*/
public static int update(String sql, Object... args) throws Exception {
Connection conn = null;
PreparedStatement ps = null;
try {
// 获取连接
conn = JDBCUtil.getConnection();
ps = (PreparedStatement) conn.prepareStatement(sql);
// 填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
return ps.executeUpdate();
} catch (Exception e) {
throw e;
} finally {
JDBCUtil.close(null, ps, conn);
}
}
/**
* 查询操作
*
* @param clazz
* @param sql
* @param args
* @return
* @throws Exception
*/
public static <T> List<T> selects(Class<T> clazz, String sql, Object... args) throws Exception {
Connection conn = null;
PreparedStatement preparedStatement = null;
ResultSet executeQuery = null;
try {
List<T> list = new ArrayList<>();
conn = JDBCUtil.getConnection();
preparedStatement = (PreparedStatement) conn.prepareStatement(sql);
// 填充占位符
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
// 查询结果集
executeQuery = preparedStatement.executeQuery();
ResultSetMetaData metaData = (ResultSetMetaData) executeQuery.getMetaData();
int columnCount = metaData.getColumnCount();
while (executeQuery.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
Object columnVal = executeQuery.getObject(i + 1);
String columnLabel = metaData.getColumnLabel(i + 1);
// 设置属性值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnVal);
}
list.add(t);
}
return list;
} catch (Exception e) {
throw e;
} finally {
JDBCUtil.close(executeQuery, preparedStatement, conn);
}
}
}
Tip
:DAO的设计可以借鉴该处的思想
2、jdbc.properties配置文件
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test?useServerPrepStmts=true&cachePrepStmts=true
user=root
password=abc123
Tip
:useServerPrepStmts参数的作用是开启SQL预编译功能,cachePrepStmts参数的作用是缓存编译后的函数(这样即使使用不同的PreparedStatement对象执行相同的SQL操作时也不会重新编译SQL文)
二、Statement和PreparedStatement
详见:深入理解PreparedStatement和Statement
另外PreparedStatement还拥有批量处理的功能,参看:Statement和PreparedStatement的区别
三、数据库事务
public void updateWithTransaction() {
Connection conn = null;
try {
// 获取数据库连接
conn = JDBCUtil.getConnection();
// 开启事务
conn.setAutoCommit(false);
// 具体的转账操作
String sql1 = "update user_table set balance = balance - ? where user = ?";
PreparedStatement prepareStatement = conn.prepareStatement(sql1);
prepareStatement.setObject(1, 500);
prepareStatement.setObject(2, "AA");
prepareStatement.executeUpdate();
prepareStatement.close();
// 异常
System.out.println(10 / 0);
String sql2 = "update user_table set balance = balance + ? where user = ?";
PreparedStatement prepareStatement2 = conn.prepareStatement(sql2);
prepareStatement2.setObject(1, 500);
prepareStatement2.setObject(2, "AA");
prepareStatement2.executeUpdate();
prepareStatement2.close();
// 提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally {
JDBCUtil.close(null, null, conn);
}
}
四、数据库连接池
在进行数据库操作的时候如果频繁的建立数据库连接,对操作系统和服务器的开销都很大,因此可以建议一个数据库连接池,将数据库连接放在池中,在需要使用数据库连接的时候直接从池中获取,这样就避免了重复建立数据库连接。数据库连接池提供商实际上将jdbc的常用类进行了再次包装,比如Connecton、Statement等,包装时使用了装饰者模式,比如从数据库连接池中获取的Connnection虽是一个实现了java.sql.Connection接口的类,但经过装饰之后,再调用其close()方法时并不会关闭数据库的物理连接,而是将其归还到连接池中。注意使用数据库连接池获取到的数据库连接在使用完之后也是需要关闭的,不然就不会归还到数据库连接池中
。
1、c3p0连接池
使用c3p0数据库连接池需要c3p0-0.9.1.2.jar包的支持
使用步骤:
①在src下新建c3p0-config.xml配置文件
<c3p0-config>
<named-config name="c3p0">
<!-- 连接四要素 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 当连接池中的连接数不够时,c3p0数据源一次性向数据库服务器申请的连接数 -->
<property name="acquireIncrement">5</property>
<!-- 初始化时连接池中的连接数 -->
<property name="initialPoolSize">10</property>
<!-- 连接池中维护的最少连接数 -->
<property name="minPoolSize">5</property>
<!-- 连接池中维护的最大连接数 -->
<property name="maxPoolSize">100</property>
<!-- 连接池中维护的最大Statement个数 -->
<property name="maxStatements">100</property>
<!-- 每个连接中最多维护的Statement个数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
②通过配置文件获取数据库连接
public Connection getConnection() throws Exception{
//读取c3p0-config.xml中的配置
DataSource cpds = new ComboPooledDataSource("c3p0");
//从连接池中获取数据库连接
return cpds.getConnection();
}
2、dbcp
使用dbcp需要commons-dbcp-1.4.jar和commons-pool-1.5.5.jar的支持。
使用步骤:
①在src下dbcp.properties配置文件,内容如下:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=123456
maxActive=50 #最大连接数
initialSize=10 #初始化连接数
②获取数据库连接
public Connection getConnection() throws Exception {
Properties pros = new Properties();
pros.load(this.getClass().getClassLoader().getResourceAsStream("dbcp.properties"));
//从数据库连接池中获取连接
DataSource source = BasicDataSourceFactory.createDataSource(pros);
return source.getConnection();
}
注意
:在DAO中的Connection不能声明为全局的,即Connection是不能在多个数据库操作中共用的,因为如果共用的话,一旦在某个方法中关闭了connection,在其他的地方就不能再使用Connection了,另外多个数据库操作共用同一个connection也无法进行事务控制。数据库连接池的意思并不是共用一个数据库连接(Connection),而是共用一个DataSource,使系统和该DataSource之间的尽可能少建立数据库连接,所以应该讲DataSource声明成全局的,让多个获取数据库连接的方法共用一个数据源。
public class DBCPUtil{
private static DataSource source = null;
static{
Properties pros = new Properties();
try{
pros.load(DBCPUtil.class.getClassLoader().getResourceAsStream("dbcp.properties"));
source = BasicDataSourceFactory.createDataSource(pros);
}catch(Exception e){
e.printStackTrace();
}
}
public static Connection getConnection(){
Connection conn = null;
try{
conn = source.getConnection();
}catch(SQLException e){
e.printStackTrace();
}
return conn;
}
}