jdbc笔记(二)事务
2014-04-20 08:07
375 查看
事务
一、事务的隔离级别
数据库系统要负责隔离操作,写代码只需要对隔离级别进行设置。
1,如果不考虑事务的隔离级别,会出现以下问题(不正确的)
a,脏读:一个事务读取到了另一个事务“未提交”的数据。
b,不可重复读:在一个事务内读取表中的某一行数据,多次读取结果不同。
c,虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
2,MySQL中操作事务隔离级别的命令
mysql>>select @@tx_isolation; #查看当前事务隔离级别
mysql>>set transaction isolation level 你的级别 #更改数据库当前事务隔离级别
隔离级别有四个级别(需要记住,注意是可能):
READ UNCOMMITTED:脏读,不可重复读,虚读都有可能发生
READ COMMITTED:防止脏读的发生,但是不可重复读,虚读都有可能发生
*REPEATABLE READ:防止脏读,不可重复读的发生,但是虚读有可能发生(开发中用这个,数据库默认的)
SERIALIZABLE:防止脏读,不可重复读,虚读的发生,(提升到这个级别后,数据库会把整张表都锁住)
隔离级别原理,其实就是给行、表、数据库都加了一个锁。最高的隔离级别,必然有性能的问题,他锁了整张表,别人无法访问。
独占锁:增删改查都锁住了 共享锁:别人只能查(了解)
悲观锁:理论上可能发生的都要防住。乐观锁:只要基本上不发生的,就不加锁.
在设置隔离级别的时候,只是用当前命令行进行设置即可。
想要永久设置,需要在my.ini配置文件中修改,但最好不要修改配置文件
其他的设置在文档中的连接里面查。
public static void main(String[] args) throws Exception { Connection conn = JdbcUtil.getConnection(); //一定要在开启事务前进行隔离级别设置 conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); //开启事务,(把自动提交事务关闭) conn.setAutoCommit(false); PreparedStatement stmt = conn.prepareStatement("select * from account where name='aaa'"); ResultSet rs = stmt.executeQuery(); if(rs.next()){ System.out.println(rs.getInt("money")); } Thread.sleep(20000);//模拟被别的线程占用 stmt = conn.prepareStatement("select * from account where name='aaa'"); rs = stmt.executeQuery(); if(rs.next()){ System.out.println(rs.getInt("money")); } //事务进行提交 conn.commit(); JdbcUtil.release(rs, stmt, conn); } |
二、数据库连接池原理,建立数据源
用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。
进行优化:创建一个连接池(缓存),把连接放进去,请求的时候取出连接使用,用完后放回去。(Tomcat就是使用此方式)
DataSourse(所有的框架都是接触这个数据源,一般数据源都是带连接池的)
数据源,连接物理连接:getConnection(),是在缓冲中去取
//导入javax.sql.DataSource public class MyDataSource implements DataSource{ private static String driverClass; private static String url; private static String user; private static String password; //就是所谓的连接池,(创建链表数组,本身具有排序功能) private static LinkedList<Connection> pool = new LinkedList<Connection>(); static{ try{//读取配置文件 InputStream in = MyDataSource.class.getClassLoader().getResourceAsStream("dbcfg.properties"); Properties props = new Properties(); props.load(in); driverClass = props.getProperty("driverClass"); url = props.getProperty("url"); user = props.getProperty("user"); password = props.getProperty("password"); //加载驱动.创建了这个类的实例,这个类静态方法加载驱动 Class.forName(driverClass); //创建10个连接,并存入到连接池中 for(int x=0;x<10;x++){ Connection conn = DriverManager.getConnection(url, user, password); pool.add(conn); } }catch(Exception e){ //一般会抛这个异常 throw new ExceptionInInitializerError(); } } //从池中获取数据库连接,因为可能有很多人同时访问,需要加同步锁 public synchronized Connection getConnection(){ if(pool.size()>0){ Connection conn = pool.remove();//取得同步锁,并把连接从连接池中删除 MyConnection1 myconn = new MyConnection1(conn, pool); return myconn; }else{//如果池中没有连接,则抛出异常 throw new RuntimeException("对不起!服务器忙"); } } |
建立了jdbcpoolutil
建立了MyDataSource
建立了SomeDao,进行测试,需要调用MyDataSource和MyConnection1
public class SomeDao { private DataSource ds; public SomeDao(DataSource ds){ this.ds = ds; } public void add(){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try{ //传进来的是MyDataSource,其实是从缓存中取 conn = ds.getConnection(); stmt = conn.prepareStatement("select * from account"); rs = stmt.executeQuery(); while(rs.next()){ System.out.println(rs.getString("name")); } }catch(Exception e){ throw new RuntimeException(e); }finally{ 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 (Exception e) { e.printStackTrace(); } conn = null; } } } public static void main(String[] args) { SomeDao dao = new SomeDao(new MyDataSource()); dao.add(); } } |
包装设计模式:扩展原有类的原有方法
1,写子类,覆盖对应的方法(Connection行不通,因为父类里面有信息,而自己的子类没有,而且覆盖的close也没有用)
2,经常用:包装设计模式(IO流里面的包装类大量存在)
//目标:扩展close方法,不要关闭链接,要还回池中 //java.sql.Connection:被包装类 //自己写的:包装类 MyConnection1 //a、编写包装类,实现与被包装类相同的接口(使他们有相同的行为) //b、定义一个变量,引用被包装的对象(记住原有对象的信息) //c、定义包装类的构造方法,传入被包装对象的引用(给b赋值) //d、对于要改写的方法(比如close),编写你自己的代码即可(功能的扩展) //e、对于不需要改写的方法,调用原有对象的。 public class MyConnection1 implements Connection { private Connection conn;//原来的conn,针对所有的驱动 private LinkedList<Connection> pool;//原来的池 public MyConnection1(Connection conn,LinkedList<Connection> pool) { this.conn = conn; this.pool = pool; } //还回池中 public void close() throws SQLException{ pool.addLast(conn); } |
ConnectionWrapper:默认适配器 Wrapper后缀
就是把其实现的接口中的所有方法都实现,但是这个类定义为抽象类。那么使用的时候,只要覆盖自己想覆盖的方法,就可以了
有了默认适配器ConnectionWrapper后,可以直接写一下包装类:
public class MyConnection2 extends ConnectionWrapper { private Connection conn; private LinkedList<Connection> pool;//原来的池 public MyConnection2(Connection conn,LinkedList<Connection> pool){ super(conn); this.pool = pool; } public void close() throws SQLException { //还回池中 pool.addLast(conn); } } |
四、基于接口的动态代理(有拦截某个方法的能力)
基于接口的动态代理:被代理对象必须实现某一个接口或者某一些接口
基于子类的动态代理:被代理类,必须是public,CGLIB。
动态代理:字节码由虚拟机随时用随时生成返回代理对象的实例
静态代理:就是代理类已经写好了的的代理
因为被代理对象必须实现某一个接口或者某一些接口(接口中方法的权限默认是public)
public interface Human { void sing(float money); void dance(float money); void eat(); } |
被代理对象,实现一个接口
public class SpringBrother implements Human { public void sing(float money) { System.out.println("拿到:"+money+"钱,开唱"); } public void dance(float money) { System.out.println("拿到:"+money+"钱,开跳"); } public void eat() { System.out.println("开吃"); } } |
认识并使用动态代理,从下往上看
public class Client { public static void main(String[] args) { final Human h = new SpringBrother(); //newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) //返回代理对象的实例 //loader:类加载器,和被代理对象使用相同 //interfaces:代理对象要实现的接口。和被代理对象相同 //h:策略模式的使用。 // Human proxH = (Human) Proxy.newProxyInstance(h.getClass().getClassLoader(), h.getClass().getInterfaces(), new InvocationHandler1()); // Human proxH = (Human) Proxy.newProxyInstance(h.getClass().getClassLoader(), h.getClass().getInterfaces(), new InvocationHandler2()); // Human proxH = (Human) Proxy.newProxyInstance(h.getClass().getClassLoader(), h.getClass().getInterfaces(), new InvocationHandler3(h)); //内部类对象实现 Human proxH = (Human) Proxy.newProxyInstance(h.getClass().getClassLoader(), h.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("sing".equals(method.getName())){ float money = (Float) args[0]; if(money<10000){ System.out.println("你给的钱太少了!"); }else{ h.sing(money/2); } }else if("dance".equals(method.getName())){ float money = (Float) args[0]; if(money<10000){ System.out.println("你给的钱太少了!"); }else{ h.dance(money/2); } }else{//其他的方法再执行,并返回执行结果 return method.invoke(h, args); } return null; }}); proxH.dance(40000); proxH.sing(200); proxH.eat(); } } //3B经纪人 class InvocationHandler3 implements InvocationHandler{ private Human h; public InvocationHandler3(Human h){ this.h=h; } //proxy:对代理对象的引用 //method:当前执行的是什么方法 //args:执行方法用到的参数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("sing".equals(method.getName())){ //注意参数是0, float money = (Float) args[0]; if(money<10000){ System.out.println("你给的钱太少了"); }else{ h.sing(money); } } if("dance".equals(method.getName())){ //注意参数是0, float money = (Float) args[0]; if(money<10000){ System.out.println("你给的钱太少了"); }else{ h.dance(money); } } return null; } } //2B经纪人 class InvocationHandler2 implements InvocationHandler{ //proxy:对代理对象的引用 //method:当前执行的是什么方法 //args:执行方法用到的参数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("sing".equals(method.getName())){ //唱歌的代理 System.out.println("唱歌的,我要代理"); }if("dance".equals(method.getName())){ //跳舞我要代理 System.out.println("跳舞的我要代理"); } return null; } } //SB经纪人 class InvocationHandler1 implements InvocationHandler{ //调用被代理对象的任何方法,都会经过该方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理了"+method.getName()); return null; } } |
用动态类来改数据源
public class MyDataSource implements DataSource { private static String driverClass; private static String url; private static String user; private static String password; private static LinkedList<Connection> pool = new LinkedList<Connection>(); static{ try{ InputStream in = MyDataSource.class.getClassLoader().getResourceAsStream("dbcfg.properties"); Properties props = new Properties(); props.load(in); driverClass = props.getProperty("driverClass"); url = props.getProperty("url"); user = props.getProperty("user"); password = props.getProperty("password"); Class.forName(driverClass); for(int x=0;x<10;x++){ Connection conn = DriverManager.getConnection(url, user, password); pool.add(conn); } }catch(Exception e){ throw new ExceptionInInitializerError(e); } } //从池中获取数据库链接,别忘了加同步代码块 public synchronized Connection getConnection() throws SQLException { if(pool.size()>0){ final Connection conn = pool.remove();//从池中获取数据 return (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //如果是close方法,则还回池中 if("close".equals(method.getName())){ pool.addLast(conn); //以下语句是一样的,这里需要返回值,所以必须要有return // return pool.addLast(conn); }else{//如果不是,则调用原有方法 return method.invoke(conn, args); } return null; } }); } return null; } |
五、常用开源数据源的使用
DBCP: Apache开发的
拷入commons-dbcp-1.4-sources.jar包。
然后再拷贝dbcpconfig.properties文件到src中(常用配置参数)
建立DBCPUtil工具类,使用导入的jar包
//DBCP数据源的获取 public class DBCPUtil { private static DataSource ds; static{ try { //用反射获取InputStream流 InputStream in = DBCPUtil.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"); Properties props = new Properties(); props.load(in); //用DBCP中的方法获取数据源 ds = BasicDataSourceFactory.createDataSource(props); } catch(Exception e) { throw new ExceptionInInitializerError(e); } } public static DataSource getDataSource(){ return ds; } public static Connection getConnection(){ try { return ds.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } } public static void realease(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 (Exception e) { e.printStackTrace(); } conn = null; } } } |
如果人很多,连接池不够,那么会创建10个连接。(是什么状况?)
C3P0数据源
拷3个jar包:
c3p0-0.9.1.2.jar
c3p0-0.9.1.2-jdk1.3.jar
c3p0-oracle-thin-extras-0.9.1.2.jar
写C3P0Util工具类
public class C3P0Util { private static ComboPooledDataSource cpds = new ComboPooledDataSource(); public static DataSource getDataSource(){ return cpds; } public static Connection getConnection(){ try { return cpds.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } } public static void realease(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 (Exception e) { e.printStackTrace(); } conn = null; } } } |
六、利用Tomcat管理数据源:JNDI
Tomcat里面就有下面两个jar包,但是别忘了拷贝数据库的jar包:
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
Tomcat已经集合了这两个jar包tomcat-dbcp.jar,使用的时候会用就行了。
还有为了能够使用MySQL数据库,还应该拷贝mysql-connector-java-5.0.8-bin.jar这个jar包。
拷贝jar后,需要重启服务器
JNDI容器: Java Naming and Directory Interface
如果配置了Tomcat管理数据源,Tomcat在启动时,会利用JNDI技术把DataSource实例绑定到JNDI容器中.
指挥Tomcat去做:配置文件。Tomcat里面的文档 JND IDataSource()
按照以下步骤:
1、把数据库驱动拷贝到Tomcat\lib目录下(其实已经存在)
2、在你应用的META-INF目录下建立一个context.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="root" password="sorry" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/day12"/>
</Context>
注意:也可以在服务器的config中的contex中去配,但是这样会进行全局配置。
3、在web环境下如何获取数据源:(插入java代码)
Context initContext = new InitialContext();
DataSource ds = (DataSource)initContext.lookup("java:/comp/env/jdbc/TestDB");
Connection conn = ds.getConnection();
注意:context导包的时候,要导javax.naming的包。其他的包导java.sql.*即可
JNDI容器就相当于windows里面的注册表,树状结构
context.xml中的name="jdbc/TestDB"(对应web环境下获取数据源的地址)名字可以随便起,有意义,方便看清楚这个东西是干什么的.
第3步里,Tomcat里面里去得,就是web环境。才可以得到数据源
如果在Class里面运行,这是一个新的main在运行,跟Tomcat没有关系,就不能得到数据源
相关文章推荐
- JavaWeb笔记之JDBC事务操作
- jdbc复习笔记-事务的特性(ACID)及事务隔离性
- 【JDBC笔记】JDBC的批处理、事务等
- 课堂笔记--JDBC 事务控制和管理
- JDBC 学习笔记(十一)—— JDBC 的事务支持
- SpringMVC学习笔记三 整合jdbc和事务
- JDBC基础学习笔记_04_jdbc事务处理
- 学习笔记---------------------spring2.5+jdbc(jdbctemplate)+事务管理
- JDBC笔记(七)事务
- web 学习笔记15-JDBC大数据 批处理 存储过程 事务
- [Spring学习笔记 7 ] Spring中的数据库支持 RowMapper,JdbcDaoSupport 和 事务处理Transaction
- 9-15课堂笔记JDBC的事务控制和批量处理
- JDBC 笔记3: JDBC事务并发产生的问题和事务隔离级别
- JDBC学习笔记——事务、存储过程以及批量处理
- JDBC【事务、元数据、改造JDBC工具类】
- 【SSH】Spring学习(三)Spring整合JDBC、Spring中AOP事务
- Java_jdbc 基础笔记之九 数据库连接 (查询Customer对象的方法)
- JDBC事务管理及SavePoint示例
- JDBC之事务隔离级别以及ACID特性
- JDBC(2)JAVA连接数据库之 分页操作、事务操作、代理模式