9、数据库事务
-> 一组要么同时执行成功,要么同时执行失败的SQL语句。是数据库操作的一个执行单元。
9.1、事务概述
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执
行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更
新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误
恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、
隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务
的处理。
事务开始于
- 连接到数据库上,并执行一条DML语句insert、update或delete
- 前一个事务结束后,又输入了另一条DML语句
事务结束于
- 执行commit或rollback语句。
- 执行一条DDL语句,例如create table语句,在这种情况下,会自动执行commit语句。
- 执行一条DDL语句,例如grant语句,在这种情况下,会自动执行commit。
- 断开与数据库的连接
- 执行了一条DML语句,该语句却失败了,在这种情况中,会为这个无效的DML语句执行rollback语
句
9.2、事务的四大特点
(ACID)
- actomicity(原子性)
表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败 - consistency(一致性)
表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前状态 - isolation(隔离性)
事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修
改它之后的状态,事务不会查看中间状态的数据。 - durability(持久性)
持久性事务完成之后,它对于系统的影响是永久性的。
9.3、 JDBC中事务应用
如果JDBC连接处于自动提交模式,默认情况下,则每个SQL语句在完成后都会提交到数据库。
事务使您能够控制是否和何时更改应用于数据库。它将单个SQL语句或一组SQL语句视为一个逻辑单元,如果任何语句失败,则整个事务将失败。
要启用手动事务支持,而不是JDBC驱动程序默认使用的自动提交模式,请使用Connection对象的setAutoCommit()方法。如果将boolean false传递给setAutoCommit(),则关闭自动提交。我们可以传递一个布尔值true来重新打开它。
9.4、事务的提交和回滚
完成更改后,我们要提交更改,然后在连接对象上调用commit()方法,如下所示:
conn.commit( );
否则,要使用连接名为conn的数据库回滚更新,请使用以下代码 -
conn.rollback( );
9.5、Savepoints
新的JDBC 3.0 Savepoint接口为您提供了额外的事务控制。
设置保存点时,可以在事务中定义逻辑回滚点。如果通过保存点发生错误,则可以使用回滚方法来撤消所有更改或仅保存在保存点之后所做的更改。
Connection对象有两种新的方法来帮助您管理保存点 -
- setSavepoint(String savepointName):定义新的保存点。它还返回一个Savepoint对象。
- releaseSavepoint(Savepoint savepointName):删除保存点。请注意,它需要一个Savepoint
对象作为参数。此对象通常是由setSavepoint()方法生成的保存点。
1、要取消掉JDBC的自动提交:void setAutoCommit(boolean autoCommit)
2、执行各个SQL语句,加入到批处理之中
3、如果所有语句执行成功,则提交事务 commit();如果出现了错误,则回滚:rollback()
数据库事务通常要借助补捉异常语句
10.JDBC批处理
批量处理允许您将相关的SQL语句分组到批处理中,并通过对数据库的一次调用提交它们。
当您一次向数据库发送多个SQL语句时,可以减少连接数据库的开销,从而提高性能。
10.1、 Statement批处理
以下是使用语句对象的批处理的典型步骤序列
- 使用createStatement()方法创建Statement对象。
- 使用setAutoCommit()将auto-commit设置为false 。
- 使用addBatch()方法在创建的语句对象上添加您喜欢的SQL语句到批处理中。
- 在创建的语句对象上使用executeBatch()方法执行所有SQL语句。
- 最后,使用commit()方法提交所有更改。
Statement stmt = conn.createStatement();
conn.setAutoCommit(false);
//sql1
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(200,'Zia',
'Ali', 30)";
stmt.addBatch(SQL);
//sql2
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(201,'Raj',
'Kumar', 35)";
stmt.addBatch(SQL);
//sql3
String SQL = "UPDATE Employees SET age = 35 WHERE id = 100";
stmt.addBatch(SQL);
int[] count = stmt.executeBatch();
conn.commit();
10.2、PreparedStatement批处理
- 使用占位符创建SQL语句。
- 使用prepareStatement() 方法创建PrepareStatement对象。
- 使用setAutoCommit()将auto-commit设置为false 。
- 使用addBatch()方法在创建的语句对象上添加您喜欢的SQL语句到批处理中。
- 在创建的语句对象上使用executeBatch()方法执行所有SQL语句。
- 最后,使用commit()方法提交所有更改。
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(?, ?, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(SQL);
conn.setAutoCommit(false);
// Set the variables
pstmt.setInt( 1, 400 );
pstmt.setString( 2, "Pappu" );
pstmt.setString( 3, "Singh" );
pstmt.setInt( 4, 33 );
// Add it to the batch
pstmt.addBatch();
// Set the variables
pstmt.setInt( 1, 401 );
pstmt.setString( 2, "Pawan" );
pstmt.setString( 3, "Singh" );
pstmt.setInt( 4, 31 );
// Add it to the batch
pstmt.addBatch();
//add more batches
//Create an int[] to hold returned values
int[] count = stmt.executeBatch();
//Explicitly commit statements to apply changes
conn.commit();
11、反射处理结果集
接口:
//查询所有的学生信息
public List<Student> findallstudent(Class cla);
实现类:
@Override
public List<Student> findallstudent(Class cla) {
Connection con = null;
PreparedStatement pps = null;
ResultSet rs =null;
try {
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/yhp",
"root", "123456");
List list=new ArrayList();
String sqla = "select * from student";
pps = con.prepareStatement(sqla);
rs = pps.executeQuery();
//得到数据库中的所有的列有哪些?
ResultSetMetaData metaData = rs.getMetaData();//返回数据库中的相关信息
int count=metaData.getColumnCount();//得到列数
String[] columnnames=new String[count];
for (int i = 0; i < count; i++) {
// System.out.println(metaData.getColumnName(i+1));//列的位置从1开始
columnnames[i]=metaData.getColumnName(i+1);
}
//得到实体类中的所有的方法
Method[] methods =cla.getDeclaredMethods();
while(rs.next()){
Object s=cla.newInstance();//调取无参构造创建对象
for (String columnname : columnnames) {
String name="set"+columnname;//setstuid
for (Method method : methods) {
if(method.getName().equalsIgnoreCase(name)){
method.invoke(s,rs.getObject(columnname));//执行了对应的
set方法
break;
}
}
}
list.add(s);
}
System.out.println("执行成功");
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null) {
rs.close();
}
if (pps != null) {
pps.close();
}
if (con != null) {
con.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
12、工具类的定义
public class DbUtils {
//1.定义需要的工具类对象
protected Connection connection=null;
protected PreparedStatement pps=null;
protected ResultSet rs=null;
protected int k=0;//受影响的行数
private String url="jdbc:mysql://localhost:3306/yhp";
private String username="root";
private String password="123456";
//2.加载驱动
static{
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//3.获得连接
protected Connection getConnection(){
try {
connection=DriverManager.getConnection(url,username,password);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
//4.创建通道
protected PreparedStatement getPps(String sql){
try {
getConnection();//insert into users values(?,?,?,?,)
pps=connection.prepareStatement(sql);
} catch (SQLException e) {
e.printStackTrace();
}
return pps;
}
//5.给占位符赋值 list中保存的是给占位符所赋的值
private void setParams(List list){
try {
if(list!=null&&list.size()>0){
for (int i=0;i<list.size();i++) {
pps.setObject(i+1,list.get(i));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//6.增删改调取的方法
protected int update(String sql,List params){
try {
getPps(sql);
setParams(params);
k= pps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
return k;
}
//7.查询的时候调取一个方法
protected ResultSet query(String sql,List list){
try {
getPps(sql);
setParams(list);
rs=pps.executeQuery();
return rs;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//8.关闭资源
protected void closeall(){
try {
if (rs != null) {
rs.close();
}
if (pps != null) {
pps.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
12.1、 properties文件保存数据库信息-特点:key-value存储方式
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/yhp
user=root
password=123456
工具类中读取属性文件:
InputStream inputStream = 当前类名.class.getClassLoader()
.getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(inputStream);
dirverName = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("user");
password = properties.getProperty("password");
方式2:
static{
//参数只写属性文件名即可,不需要写后缀
ResourceBundle bundle = ResourceBundle.getBundle("db");
driver = bundle.getString("driver");
url = bundle.getString("url");
username = bundle.getString("user");
password = bundle.getString("password");
}
说明:
使用ResourceBundle访问本地资源
在设计时,我们往往需要访问一些适合本地修改的配置信息,如果作为静态变量,那么每次修改都需要重新编译一个class,.config保存此类信息并不适合,
这时我们需要 ResourceBundle。
通过ResourceBundle,我们需要访问位于/WEB-INF/classes目录下的一个后缀名为properties的文本类型文件,从里面读取我们需要的值。
13、连接池
数据连接池原理
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数
据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户
也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连
接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数
以及每个连接的最大使用次数、最大空闲时间等等,也可以通过其自身的管理机制来监视数据库连接的
数量、使用情况等。
13.1 自定义连接池
我们可以通过自定义的方式实现连接池!分析连接池类应该包含特定的属性和方法!
属性: 集合 放置Connection
方法: 获取连接方法
回收连接方法
具体实现代码:
public class Pool{
static LinkedList<Connection> list = new LinkedList<Connection>();
static{
for (int i = 0; i < 10; i++) {
Connection connection = JDBCUtils.newInstance().getConnection();
list.add(connection);
}
}
/**
* 从连接池子中获取连接的方式
* @return
*/
public static Connection getConnection(){
if (list.isEmpty()) {
//JDBCUtils类是自定义类,封装了连接数据库的信息代码
Connection connection = JDBCUtils.newInstance().getConnection();
list.addLast(connection);
}
Connection conn = list.removeFirst();
return conn;
}
/**
* 返回到连接池子中
*/
public static void addBack(Connection conn){
if (list.size() >= 10) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
list.addLast(conn); //10
}
}
/**
* 获取连接池子中连接数量的方法
*/
public static int getSize(){
return list.size();
}
}
13.2 java规范实现连接池
Java为连接池实现提供了一个规范(接口),规范的写法,我们需要实现DataSource接口!
但是实现DataSource接口有一个弊端,没有提供回收链接方法!这里我们将使用装饰者模式
装饰Connection!具体实现代码如下:
- 创建装饰Connection
public class MyConnection implements Connection{
//将被装饰者导入
private Connection conn;
private LinkedList<Connection> list;
public MyConnection(Connection conn, LinkedList<Connection> list) {
super();
this.conn = conn;
this.list = list;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return conn.unwrap(iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return conn.isWrapperFor(iface);
}
@Override
public Statement createStatement() throws SQLException {
return conn.createStatement();
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return conn.prepareStatement(sql);
}
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
return null;
}
@Override
public String nativeSQL(String sql) throws SQLException {
return null;
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
}
@Override
public boolean getAutoCommit() throws SQLException {
return false;
}
@Override
public void commit() throws SQLException {
conn.commit();
}
@Override
public void rollback() throws SQLException {
conn.rollback();
}
@Override
public void close() throws SQLException {
list.addLast(conn);
}
...
}
基于规范实现的连接池
/**
* 创建一个规范的连接池子
*/
public class DataSourcePool implements DataSource{
static LinkedList<Connection> list = new LinkedList<Connection>();
static{
for (int i = 0; i < 10; i++) {
Connection connection = JDBCUtils.newInstance().getConnection();
list.add(connection);
}
}
public static int getSize(){
return list.size();
}
@Override
public Connection getConnection() throws SQLException {
Connection conn = list.removeFirst();
MyConnection conn1 = new MyConnection(conn, list);
return conn1;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public Connection getConnection(String username, String password) throws
SQLException {
return null;
}
}
最小连接数:
是数据库一直保持的数据库连接数,所以如果应用程序对数据库连接的使用量不大,将有大量的数据库资源被浪费。
初始化连接数:
连接池启动时创建的初始化数据库连接数量。
最大连接数:
是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求被加入到等待队列中。
最大等待时间:
当没有可用连接时,连接池等待连接被归还的最大时间,超过时间则抛出异常,可设置参数为0或者负数使得无限等待(根据不同连接池配置)。
常用:Druid(德鲁伊)连接池
使用步骤:
1 导入jar包
2 编写工具类
/**
* 阿里的数据库连接池
* 性能最好的
* Druid
* */
public class DruidUtils {
//声明连接池对象
private static DruidDataSource ds;
static{
///实例化数据库连接池对象
ds=new DruidDataSource();
//实例化配置对象
Properties properties=new Properties();
try {
//加载配置文件内容
properties.load(DruidUtils.class.getResourceAsStream("dbcpconfig.properties"));
//设置驱动类全称
ds.setDriverClassName(properties.getProperty("driverClassName"));
//设置连接的数据库
ds.setUrl(properties.getProperty("url"));
//设置用户名
ds.setUsername(properties.getProperty("username"));
//设置密码
ds.setPassword(properties.getProperty("password"));
//设置最大连接数量
ds.setMaxActive(Integer.parseInt(properties.getProperty("maxActive")));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//获取连接对象
public static Connection getConnection() {
try {
return ds.getConnection();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
注:在Druid连接池的配置中,driverClassName可配可不配,如果不配置会根据url自动识别dbType(数
据库类型),然后选择相应的driverClassName