您的位置:首页 > 其它

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();
    }
}
三、编程难点:调用链接的close()方法,不要关闭链接,还回池中
包装设计模式:扩展原有类的原有方法

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);
    }
确认包装的法宝:System.out.println(*.class.getName());打印看看
 
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没有关系,就不能得到数据源
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: