Java的数据库开发使用JDBC,它的开发步骤有:1.注册驱动;2.获得链接;3.执行sql;4.释放资源。
数据库的事务,是指逻辑上不可分割的一组操作,要么全成功要么全失败。
MYSQL的事务开启方式:
start transaction; --开启事务 commit; --提交事务 rollback; --事务回滚
JDBC设计有事务保存点,可以使回滚操作到这个保存点。
Savepoint savePoint = conn.setSavepoint();
事务所具有的特性:1.原子性;2.一致性;3.隔离性;4.持久性。
1.强调事务不可分割;
2.事务执行的时候数据完整性保持一致;
3.一个事务的执行,不应该受到另外一个事务的影响;
4.事务执行完毕,数据成为持久化的存在。
由于事务的隔离性,数据库事务产生三种读的问题:脏读,不可重复读,幻读。
脏读:一个事务读到了另一个事务未提交的数据。
当事务隔离级别为read uncommitted的时候,一个事务执行到一半并未提交,另一个事务读到了数据,前一个事物回滚,这样第二个事务读到的就是不真实的数据。
不可重复读:一个事务读取到了另一个事务已经提交的数据(一般执行的事update操作)
当事务第一次读取的时候,此时它需要进行两次查询,同时另一个事务的更新处于两次查询之间;另一个事务更新完后再查询的时候数据是不相同的。此时他们的隔离级别都是read committed。
幻读:一个事务读取到另外一个事务已经提交的数据(一般执行的事insert操作)
MYSQL数据库的幻读是有概率性的,不好演示。
数据库的四种隔离级别:
read uncommitted 未提交读,那么脏读、不可重复读、幻读都是有可能发生的 read committed 已提交读,可避免脏读,不能避免不可重复读和幻读 repeatable read 重复读,避免脏读、不可重复读,但是幻读还是有可能产生 serializable 串行读,避免所有异常读的产生
他们的安全性是递增的,效率性是递减的。
MYSQL默认的是repeatable read;Orical中的是read committed。
通过命令查看事务的隔离级别: * select @@tx_isolation; 设置数据库隔离级别: * set session transaction isolation level 隔离级别;
数据库连接池:由于数据库的链接对象的创建是耗时的,如果使用预先创建好的链接对象并复用,则可节省服务器的内存并提高效率。
连接池的链接的close方法设计的是把链接归还到连接池中,而不是销毁。
开源数据库连接池:DBCP,C3P0,tomcat内置连接池(jndi技术)。
ThreadLocal:一个线程包,可以把一些对象存放在被线程中,作为本线程的全局对象。它的原理是维护一个map集合,放入的k,v对象是本线程对象和需要保存的全局对象。
class MyThreadLocal<T> { private Map<Thread,T> map = new HashMap<Thread,T>(); public void set(T value) { map.put(Thread.currentThread(), value); } public void remove() { map.remove(Thread.currentThread()); } public T get() { return map.get(Thread.currentThread()); } }
service层与事务:DAO层执行的是对数据源的单个操作,service层比较适合作为事务的存放层。在service层中调用DAO层的多个方法,并且使用同一个链接对象。
实例:使用c3p0实现一个转账事务。service层-dao层-model层
模型javabean
package JDBCConectionPools; public class account { private int id; private String name; private float money; public account(){ } public account(int id, String name, float money) { super(); this.id = id; this.name = name; this.money = money; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public float getMoney() { return money; } public void setMoney(float money) { this.money = money; } }
dao层
package JDBCConectionPools; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; public class accountDAO { public List<account> selectAllAccount(Connection conn){ String sql = "select * from account"; Statement statement; ResultSet rs = null; List<account> list =new ArrayList<account>(); try { statement = conn.createStatement(); rs = statement.executeQuery(sql); while(rs.next()){ int id = rs.getInt("id"); String name = rs.getString("name"); float money = rs.getFloat("money"); account a = new account(id,name,money); list.add(a); System.out.println("id:"+id+" name:"+name+" money:"+money); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return list; } //转出账户 public void decAccount(Connection conn,String name,float number){ String sql = "update account set money = money -? where name = ?"; PreparedStatement stmt = null; ResultSet rs = null; try { stmt = conn.prepareStatement(sql); stmt.setFloat(1, number); stmt.setString(2, name); stmt.executeUpdate(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //转入账户 public void incAccount(Connection conn,String name,float number){ String sql = "update account set money = money + ? where name = ?"; PreparedStatement stmt = null; ResultSet rs = null; try { stmt = conn.prepareStatement(sql); stmt.setFloat(1, number); stmt.setString(2, name); stmt.executeUpdate(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //单个搜索 public account selectUser(Connection conn,String name){ String sql = "select * from account where name = ?"; PreparedStatement statement; ResultSet rs = null; account a = null; try { statement = conn.prepareStatement(sql); statement.setString(1, name); rs = statement.executeQuery(); if(rs.next()) { a = new account(rs.getInt("id"),rs.getString("name"),rs.getFloat("money")); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return a; } }
service层
package JDBCConectionPools; import java.sql.Connection; import java.sql.SQLException; import java.util.List; import org.junit.Test; public class accountSerivce { //搜索事务 public List<account> selectAccountService(){ Connection conn = JDBCUtils.getConnection(); accountDAO dao = new accountDAO(); List<account> list = null; try { JDBCUtils.beginTransaction(); list = dao.selectAllAccount(conn); JDBCUtils.commitTransaction(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); try { JDBCUtils.rollbackTransaction(); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } return list; } //赚钱服务 public void transferAccounts(String from,String to,float number){ Connection conn = JDBCUtils.getConnection(); accountDAO dao = new accountDAO(); try { JDBCUtils.beginTransaction(); dao.decAccount(conn, from, number); dao.incAccount(conn, to, number); account account = dao.selectUser(conn, from); if(account != null) if(account.getMoney() < 0) { System.err.println("余额不足!无法转账!"); JDBCUtils.rollbackTransaction(); } else { JDBCUtils.commitTransaction(); } else JDBCUtils.rollbackTransaction(); } catch (SQLException e) { e.printStackTrace(); try { JDBCUtils.rollbackTransaction(); } catch (SQLException e1) { e1.printStackTrace(); } } } @Test public void test(){ accountSerivce service = new accountSerivce(); service.transferAccounts("美美", "冠希", 10000); List<account> list = service.selectAccountService(); for(int i =0;i<list.size();i++) System.out.println(list.get(i).getId()+" "+list.get(i).getName()+" "+list.get(i).getMoney()); } }
工具类
package JDBCConectionPools; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import com.mchange.v2.c3p0.ComboPooledDataSource; public class JDBCUtils { private static ComboPooledDataSource cpds = null; private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); static { // 这里有个优点,写好配置文件,想换数据库,简单 cpds = new ComboPooledDataSource("mysql");// 这是mysql数据库 } public static Connection getConnection(){ Connection conn = tl.get(); try { if (conn == null) { conn = cpds.getConnection(); tl.set(conn); } } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } return conn; } //事务有关 public static void beginTransaction() throws SQLException{ Connection conn = tl.get(); if(conn == null){ conn = cpds.getConnection(); tl.set(conn); } conn.setAutoCommit(false); } public static void commitTransaction() throws SQLException{ Connection conn = tl.get(); if(conn == null){ conn = cpds.getConnection(); tl.set(conn); } conn.commit(); tl.remove(); } public static void rollbackTransaction() throws SQLException{ Connection conn = tl.get(); if(conn == null){ conn = cpds.getConnection(); tl.set(conn); } conn.rollback(); tl.remove(); } // 释放资源的方法 public static void release(ResultSet rs, Statement stmt, Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } rs = null; } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } stmt = null; } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } conn = null; } } public static void release(ResultSet rs,Statement stmt) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } rs = null; } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } stmt = null; } } public static void release(Statement stmt, Connection conn) { if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } stmt = null; } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } conn = null; } } }
配置文件c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!-- This is default config! --> <default-config> <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> </default-config> <!-- This is my config for mysql--> <named-config name="mysql"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/MyData?characterEncoding=utf-8</property> <property name="user">root</property> <property name="password">qwert123</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> </named-config> </c3p0-config>
遇到两个异常:1.查询适合使用statement不能设置?参数;2.使用中文查询的时候sql命令行可查,jdbc查不到,设置utf-8之后查到了。
设计方式总结:1.工具包提供连接对象的创建与回收,并且使用ThreadLocal保存链接对象,在事务commit之后从线程中移除链接对象。
2.service层中只执行dao层的操作,不进行jdbc编程。
3.dao层不用拿到链接对象,用参数链接对象执行sql。