一、JDBC编程
1.什么是JDBC?
JDBC即(java data base connection),是java提供的一套java数据库连接接口,是一套连接标准,各数据库厂商以及第三方对JDBC进行实现,从而生产数据库驱动;当前各主流数据库基本都显现了对JDBC的支持;也就是说掌握JDBC编程可以将你的程序连接到任意实现了JDBC的数据库;
2.JDBC编程基础
想要深入掌握JDBC编程,除了掌握基本的java基础之外,还需要java高级编程部分的反射,面向接口编程(尤其是连接池)等;本文以mysql为例只针对JDBC基础部分进行探讨,所以只需要创建一个java测试类以及相应的数据库驱动;
(使用开发工具为IDEA)
1.准备数据库驱动
访问mysql官网https://dev.mysql.com/downloads/connector/j/5.1.html,下载相应的数据库驱动jar包
2.准备目标数据库
3.创建测试类并导入数据库驱动
如图创建测试类与测试方法,在根目录下创建lib文件夹,用于放置依赖jar包,放置所需jar包后,发现依然缺少依赖,是应为IDEA不会自动引入,还需配置引入,进入打开项目配置入库点击右侧 "+" ,找到jar包并引入,然后应用,确定即可
完成后会发现报错提示消失,下来即可进入代码开发阶段;
4.开发实践
(1)数据库连接需要启动mysql数据库(以mysql数据库为例)和准备以下信息:
数据库驱动
数据库地址、端口
数据库名、用户名、密码
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class demotest {
/*
*本方法以参数的形式向获取数据库连接,
*在实际开发中,参数从方法参数列表中获取
*/
@Test
public void connection() {
//准备获取数据库连接
Connection con = null;
//Statement对象,用于向数据库发送sql语句
Statement st = null;
//数据库url包含数据库及地址端口等信息
String url = "jdbc:mysql://192.168.23.138:3306/demo";
//数据库用户名
String username = "root";
//数据库密码
String passward = "123456";
try {
//底层通过反射机制加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//获取数据库连接
con = DriverManager.getConnection(url,username,passward);
//实例化Statement对象
st = con.createStatement();
//要向数据发送的语句
String sql = "insert into student values(2017011,'张三',21)";
//向数据库发送sql,返回值为影响数据库的行数
int i = st.executeUpdate(sql);
System.out.print(i);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//数据库连接关闭(先开后关原则),由于可能发生异常导致数据库连接获取失败,需要判
//断,否则发生空指针异常
if (st!=null) st.close();
if (con != null) con.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
输出结果如图,则说明连接成功,并执行成功
注意:
statement用于执行不含参数的或字符串拼接的sql语句,此方法存在sql注入漏洞,所以不做介绍,对含参的sql语句将采用
PreparedStatement对象处理
在运行时可能出现NoClassDefFoundError异常,这是Junit缺少hamcrest-core-1.3.jar依赖,下载导入即可
(此间所用到对象以及方法深入学习可通过官方的API文档详细学习)
(2)PreparedStatement,ResultSet对象
@Test
public void demo2() {
//准备获取数据库连接
Connection con = null;
//PreparedStatement对象,用于向数据库发送sql语句
PreparedStatement st = null;
//数据库url包含数据库及地址端口等信息
String url = "jdbc:mysql://192.168.23.138:3306/demo";
//数据库用户名
String username = "root";
//数据库密码
String passward = "123456";
try {
//底层通过反射机制加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//获取数据库连接
con = DriverManager.getConnection(url,username,passward);
//向数据库发送的sql代码
String sql = "select * from student where number = ?";
//获取prepareStatement对象,并传入sql语句,对sql代码进行解析
st = con.prepareStatement(sql);
//sql语句解析后,传入参数
st.setInt(1,2017011);
//向数据库发送sql语句,返回值为ResultSet结果集
rs = st.executeQuery();
//从结果集对象中获取数据
while(rs.next()){ //rs.next()是行指针,初始指向表头
System.out.print(rs.getInt(1)+" "+ //rs.getXXX()相当于列指针
rs.getString(2)+" "+rs.getObject(3)); //参数i为第i列
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//数据库连接关闭(先开后关原则),由于可能发生异常导致数据库连接获取失败,需要判
//断,否则发生空指针异常
if(rs != null) rs.close();
if (st != null) st.close();
if (con != null) con.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
输出结果如图,则说明连接成功,并执行成功
5.优化:鉴于以上每次执行数据库操作,方法代码复用度较高,可对方法进行抽取优化成工具类
package module;
import java.sql.*;
public class Common {
/*
* 数据库连接
* driverClassName ----- com.mysql.jdbc.driver
* url ----- jdbc:mysql://ip:3306/数据库名
* */
public static Connection getConnecttion(String driverClassName,String url,String userName,String passWord) throws ClassNotFoundException, SQLException {
Class.forName(driverClassName);
return DriverManager.getConnection(url,userName,passWord);
}
/*
* 增删改
* */
public static void mathed1(String driverClassName, String url, String userName, String passWord,String sql) {
Connection con = null;
Statement st = null;
try {
con = Common.getConnecttion(driverClassName,url,userName,passWord);
st = con.createStatement();
int i = st.executeUpdate(sql);
System.out.println(i);
} catch (Exception e) {
throw new RuntimeException();
}finally {
try {
if(st!=null) st.close();
if(con!=null) con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/*
* 数据库查询
* */
public static void select(String driverClassName, String url, String userName, String passWord,String sql) {
Connection con = null;
ResultSet rs = null;
PreparedStatement ps = null;
try {
con = Common.getConnecttion(driverClassName,url,userName,passWord);
ps = con.prepareStatement(sql);
rs = ps.executeQuery(sql);
while(rs.next()){
System.out.println(rs.getInt(1)+" "+
rs.getString(2)+" "+rs.getObject(3));
}
}catch (Exception e){
throw new RuntimeException();
}finally {
try {
if(rs!=null) rs.close();
if(ps!=null) ps.close();
if(con!=null) con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Test
public void test() {
String driverClassName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://192.168.23.138:3306/demo";
String userName = "root";
String passWord = "123456";
String sql = "insert into student values(2017012,'李四',21)";
//数据库增删改
mathed1(driverClassName, url, userName, passWord,sql);
//查询
select(driverClassName, url, userName, passWord,sql);
}
}
如此在开发中调用comment类时,可以根据需求连接到不同的数据库,执行不同的sql语句;实现代码的复用,增强维护性;
二、数据库连接池
数据库连接池有许多种,我们主要介绍两种dbcp 、C3P0,这两种数据库连接池是当前主流的数据库连接池;
1.为什么使用数据库连接池:
数据库连接是一种有限的资源,频繁的创建和销毁数据库连接消耗非常多的系统资源,这一点在多用户的高并发应用程序中体现得尤为突出。对数据库连接的管理能显著影响到整个应用程序的性能指标。
数据库连接池正是针对这个问题提出来的。数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。能明显提高对数据库操作的性能
(本文首选介绍dbcp 、C3P0数据库连接池的基本配置连接获取,后文将主要以c3p0数据库连接池详细介绍从数据库连接池中获取连接以及连接的使用,基于连接池事务处理的jdbcUtils工具类的编写等)
2.dbpc数据库连接池:
以测试类方法来演示dbpc数据库的配置,以及数据库连接的获取使用
数据库连接池就好比一个汽车租赁公司,我们学校举办活动需要用车时,不需要去厂家购买汽车,只需要去汽车租赁公司租赁;在连接池创建初始化时可以配置初始时连接的数量,最大连接数量,最小连接数量,最大等待时间等;
最小连接数: 是数据库一直保持的数据库连接数,所以如果应用程序对数据库连接的使用量不大,将有大量的数据库资源被浪费。
初始化连接数:连接池启动时创建的初始化数据库连接数量。
最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求被加入到等待队列中。
最大等待时间:当没有可用连接时,连接池等待连接被归还的最大时间,超过时间则抛出异常,可设置参数为0或者负数使得无限等待(根据不同连接池配置)。
在使用之前需要导入dbpc连接池依赖
commons-dbcp2-2.7.0.jar
commons-dbutils-1.7.jar
commons-pool2-2.8.0.jar
在apache官网可下载相应版本,下载压缩包后解压,将其中的jar包导入idea项目的lib文件夹下,并在配置文件中引入即可(与之前上文的引入依赖方式相同);
@Test
public void ConPool() throws SQLException {
//创建dbcp数据库连接池
BasicDataSource dataSource = new BasicDataSource();
//设置数据库驱动
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
//设置数据库地址
dataSource.setUrl("jdbc:mysql://192.168.23.138:3306/demo");
//设置数据库用户
dataSource.setUsername("root");
//设置数据库密码
dataSource.setPassword("123456");
//最大连接数
dataSource.setMaxTotal(20);
//最大空闲连接数
dataSource.setMaxIdle(3);
//最大等待时间
dataSource.setMaxWaitMillis(1000);
//获取数据库连接
Connection con = dataSource.getConnection();
//打印数据库连接的名称
System.out.print(con.getClass().getName());
//关闭数据库连接
con.close();
}
得到如图所示输出说明配置成功并获取连接成功;
3.c3p0数据库的配置及连接获取
首先导入依赖:
c3p0-0.9.5.5.jar
mchange-commons-java-0.2.19.jar
commons-io-2.7.jar(在后文将介绍在xml配置文件中配置c3p0数据库连接池,并且我们只是单纯的测试类并非web项目,所以需要io依赖)
commons-logging-1.2.jar(单纯的测试类并非web项目,所以需要logging依赖,否则报错)
c3p0数据库连接池配置方法一:
@Test
public void ConC3p0() throws SQLException, PropertyVetoException {
//创建c3p0数据库连接池对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//通过反射机制加载数据库驱动
dataSource.setDriverClass("com.mysql.jdbc.Driver");
//设置数据库url地址、端口、数据库名
dataSource.setJdbcUrl("jdbc:mysql://192.168.23.138:3306/demo");
//设置数据库名
dataSource.setUser("root");
//设置数据库密码
dataSource.setPassword("123456");
//在总连接数小于最大连接数时,池中连接每次增加的数量
dataSource.setAcquireIncrement(5);
//设置数据库初始化连接数大小
dataSource.setInitialPoolSize(20);
//设置数据库最小连接数
dataSource.setMinPoolSize(2);
//设置数据库最大连接数
dataSource.setMinPoolSize(50);
//从连接池中获取数据库连接
Connection con = dataSource.getConnection();
//打印数据库连接信息
System.out.print(con);
//关闭数据库连接
con.close();
}
输出如图所示则说明c3p0数据库连接池,配置成功;
c3p0数据库连接池配置方法二:
c3p0数据库连接池配置还可通过xml配置文件,在项目的src目录下编写以c3p0-config.xml为文件名的配置文件,在代码中直接声明连接池对象,获取连接即可,在修改时只修改配置文件即可,不许修改源代码(配置文件名称必须是c3p0-config.xml,否则失败)
c3p0-config.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="jdbcUrl">jdbc:mysql://192.168.23.138:3306/demo</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">4</property>
<property name="maxPoolSize">50</property>
</default-config>
</c3p0-config>
在xml配置文件中配置连接池时,获取连接:
@Test
public void ConC3p0() throws SQLException, PropertyVetoException {
//创建c3p0数据库连接池对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//从连接池中获取数据库连接
Connection con = dataSource.getConnection();
//打印数据库连接信息
System.out.print(con);
//关闭数据库连接
con.close();
}
c3p0数据库连接池配置方法三:
c3p0数据库连接池第三种配置方式,是将配置文件存放在服务器中,在使用时从服务器上获取信息,这种方式被称为JNDI
在Tomcat的conf/Catalina/localhost创建以当前项目为名的xml文件(只对本项目有用)
<?xml version="1.0" edcoding="UTF-8"?>
<Context>
<Resource name="jdbc/dataSource"
factory="org.apache.naming.factory.BeanFactory"
type="com.mchange.v2.c3p0.ComboPooledDataSource"
<!--数据库连接参数-->
jdbcUrl="jdbc:mysql://192.168.23.138:3306/demo"
driverClass="com.mysql.jdbc.Driver"
user="root"
password="123456"
<!--池参数-->
acquireIncrement="5"
initialPoolSize="2"
maxPoolSize="50"
/>
</Context>
从Tomcat服务器上获取数据库连接池资源:
Context cxt = new InitialContext();
DataSource da = (DataSource)cxt.lookup("java:/comp/env/jdbc/dataSource")
Connection con = dataSource.getConnection();
System.out.print(con);
con.close()
输出如图所示,则说明配置成功;
注意:连接池对connection的close方法进行了重写,这里是父类引用指向子类对象,con.close()方法执行的是连接池的close方法,并未对连接关闭,只是将连接交还给了连接池,供连接池再次分配
三、事务处理以及优化
1.什么是数据库事务?
我的理解是在同一业务逻辑下所包含的数据库操作。
举例:以银行转账为例:A账户向B账户转账2000元为一个事务,则包含A账户金额减少2000元,B账户金额增加2000元两个操作;对该事务的处理必须满足事务的四大特征:原子性,数据一致性,隔离性,持久性;负责如果在转账期间如果系统发生异常,则会造成难以估量的后果;所以该事务执行要么成功,要么失败;在事务执行时会进行预处理,如果失败则回滚,如果成功则提交
我们将以该例来掩饰c3p0数据库连接池的使用,以及对事务的处理
2.实践
1.如图是数据库中的数据,我们将以如上转账操作,说明c3p0数据库连接池的使用,以及事务处理;
2.servers层处理:
@Test
public void run() {
try {
//开启事务
JdbcUtils.StartTransaction();
reduce("张三",2000);
add("李四",2000);
//提交事务
JdbcUtils.TransactionCommit();
} catch (SQLException e) {
try {
//若出现异常,则回滚事务
JdbcUtils.TransactionRollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
3.JdbcUtils工具类实现
package demo;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class JdbcUtils {
//创建一个静态连接池对象,静态属性实现连接池共享
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
/*连接池中的连接是一种共享资源,在多线程访问,尤其是高并发的场景下,单单使用Connection可能会
*出现线程安全问题,使用ThreadLocal<Connection>保证多个线程对共享变量的安全访问
*/
private static ThreadLocal<Connection> tl= new ThreadLocal<>();
//获取连接池对象
public static DataSource getDataSource(){
return dataSource;
}
//获取连接,如果存在事务,说明开启了事务处理,则返回事务连接,若不存在事务,则说明用户是普通
//的数据库操作,返回普通连接即可
public static Connection getConnection() throws SQLException {
Connection con = tl.get();
if(con!=null)
return con;
return dataSource.getConnection();
}
//开启事务
public static void StartTransaction() throws SQLException {
Connection con = tl.get();
if (con!=null) throw new SQLException();
con = getConnection();
con.setAutoCommit(false);
tl.set(con);
}
//提交事务
public static void TransactionCommit() throws SQLException {
Connection con = tl.get();
if (con == null) throw new RuntimeException();
con.commit();
con.close();
tl.remove();
}
//事务回滚
public static void TransactionRollback() throws SQLException {
Connection con = tl.get();
if (con == null) throw new RuntimeException();
con.rollback();
con.close();
tl.remove();
}
//事务连接的释放是在提交或回滚后处理,此处只释放普通连接
public static void ReleaseConnection(Connection connection) throws SQLException {
Connection con = tl.get();
if(con==null) connection.close();
if(con!=connection) connection.close();
}
}
dao层
public static void reduce(String name,int salary) throws SQLException {
/*TxQueryRunner是我们实现的QueryRunner的子类,简化jdbc的操作,QueryRunner底层将封装了
*PreparedStatement和ResultSet,可查看源码分析
*/
TxQueryRunner qr = new TxQueryRunner();
String sql = "update student set salary = salary-? where stu_name = ?";
Object object[] = {salary,name};
qr.execute(sql,new BeanListHandler<student>(student.class),object);
}
public static void add(String name,int salary) throws SQLException {
TxQueryRunner qr = new TxQueryRunner();
String sql = "update student set salary = salary+? where stu_name = ?";
Object object[] = {salary,name};
qr.execute(sql,new BeanListHandler<student>(student.class),object);
}
注意:
1.QueryRunner继承抽象类AbstractQueryRunner,并对其中的方法进行的实现,但都是简单的实现,在使用时,我们只需要重写其中的一部分方法,所以使用TxQueryRunner继承QueryRunner并重写其方法,定义我们自己的业务逻辑;
2. qr.query(sql,new BeanListHandler<student>(student.class),object);
方法sql为向数据库发送的sql语句,object 是sql语句中的参数集合
new BeanListHandler<student>(student.class)是ResultHandler的实现类,其中student是学生类,该类属性与数据库中学生表的属性一一对应,完成实体类与数据表的映射关系,并解析参数与数据表的数据类型等;
ResultHandler的实现类对数据表解析对应关系:
ResultHandler 转换一行
BeanListMandler 转换多行
MapMandler 转行一行为Map对象
MapListMandler 转行多行为Map对象
ScalarHandler 单行单列 返回一个Object
package demo;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import java.sql.Connection;
import java.sql.SQLException;
public class TxQueryRunner extends QueryRunner {
@Override
public int[] batch(String sql, Object[][] params) throws SQLException {
Connection con = JdbcUtils.getConnection();
int[] result = super.batch(con,sql, params);
JdbcUtils.ReleaseConnection(con);
return result;
}
@Override
public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
Connection con = JdbcUtils.getConnection();
T result = super.query(con,sql,rsh,params);
JdbcUtils.ReleaseConnection(con);
return result;
}
@Override
public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException {
Connection con = JdbcUtils.getConnection();
T result = super.query(con,sql,rsh);
JdbcUtils.ReleaseConnection(con);
return result;
}
@Override
public int update(String sql) throws SQLException {
Connection con = JdbcUtils.getConnection();
int result = super.update(con,sql);
JdbcUtils.ReleaseConnection(con);
return result;
}
@Override
public int update(String sql, Object param) throws SQLException {
Connection con = JdbcUtils.getConnection();
int result = super.update(con,sql,param);
JdbcUtils.ReleaseConnection(con);
return result;
}
@Override
public int update(String sql, Object... params) throws SQLException {
Connection con = JdbcUtils.getConnection();
int result = super.update(con,sql,params);
JdbcUtils.ReleaseConnection(con);
return result;
}
@Override
public <T> List<T> execute(String sql, ResultSetHandler<T> rsh, Object... params)
throws SQLException {
Connection con = JdbcUtils.getConnection();
List<T> result = super.execute(con,sql,rsh,params);
JdbcUtils.ReleaseConnection(con);
return result;
}
}
至此,关于c3p0数据库连接池及事务处理优化介绍完毕;留言区精确指正