JavaWeb学习笔记之Jdbc(二)

title: JavaWeb学习笔记之Jdbc(二)
tags: JavaWeb Jdbc
categories: JavaWeb Jdbc


若图片无法显示,请前往我的博客查看,相应文章链接:http://codingxiaxw.cn/2016/10/16/18-JavaWeb%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B9%8BJdbc%E4%BA%8C/

1.事务

1.1事务的四大特性(ACID)

  • 1.原子性:事务中的所有操作要么全部执行成功,要么执行全部失败。
  • 2.一致性:事务执行后,数据库状态与其它业务规则保持一致。
  • 3.隔离性:隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。
  • 4.持久性:一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中。即使提交事务后数据库马上崩溃,在数据库重启后,也必须能保证通过某种机制恢复数据。

1.2mysql中操作事务

在控制台中输入语句:start transaction;即开始事务。

在控制台中输入语句:commit transaction;即提交事务。

在控制台中输入语句:rollback;回滚事务,即在此事务中执行的操作全部无效,数据库回到start transaction;之前(前提是使用该语法前没有执行commit transaction;操作)。

1.3Jdbc中操作事务

在Jdbc中处理事务都是通过Connection对象完成的,同一事务中的所有操作,都在使用同一个Connection对象。

setAutoCommit(boolean);设置是否自动提交事务,如果为true表示自动提交(默认值就是true),也就是每条执行的sql语句都是一个单独的事务,如果设置false,那么就相当于开启了事务了。con.setAutoCommit(false);语句表示开启事务。

con.commit();提交并结束事务。

con.rollback();回滚事务。

2.事务的隔离级别

2.1事务的并发读问题

  • 脏读:读取到另一份事务未提交数据,即读到了脏数据。
  • 不可重复读:两次读取不一致。对统一记录的两次读取不一致,因为另一事务对该记录做了修改。
  • 幻读:又叫虚读。对同一张表的两次查询不一致,因为另一事务进行了插入了一条记录的操作。

2.2四大隔离级别(防止上述问题)

  • a.SERIALIZABLE(串行化):不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的。性能最差,可能导致死锁。
  • b.REPEATABLE READ(可重复读)(mysql默认级别):防止脏读和不可重复读,不能处理幻读问题。性能比a的好。
  • c.READ COMMITTED(读已提交数据)(Oracle默认级别):防止脏读,没有处理不可重复读,也没有处理幻读。性能比上述b好。
  • d.READ UNCOMMITTED(读未提交数据):可能出现任何事务并发问题。性能最好。但基本没人用。

2.3查看mysql的隔离级别

在控制台中输入语句:select @@tx_isolation;

也可以通过下面命令来设置隔离级别:set transaction isolationlevel[4选1];

2.4在Jdbc中设置隔离级别

con.setTransactionisolation[int lever];

3.数据库连接池

用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。如下图所示:
[图片上传失败...(image-d2ef69-1526285770981)]

数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤为突出.。对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池正式针对这个问题提出来的。数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。如下图所示:
[图片上传失败...(image-93302e-1526285770981)]

数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中, 这些数据库连接的数量是由最小数据库连接数来设定的.无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量.连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:

  • 1.最小连接数(MinActive):是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。

  • 2.最大连接数(MaxActive):是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作。

  • 3.如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接.不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放。

3.1池参数(所有池参数都有默认值)

设置初始化大小:connection.setInitialSize();
设置最小空闲连接数:connection.setMinIdle();
设置最大空闲连接数:connection.setMaxIdle();
设置最小连接数:connection.setMinActive();
设置最大连接数:connection.setMaxActive();
设置增量:一次创建的最小单位。
设置最大的等待时间:connection.setMaxWait();

3.2四大连接参数

连接池也是使用Jdbc中的四大连接参数和驱动jar包来完成创建连接对象。

3.3实现的接口

连接池必须实现javax.sql.DataSource接口。

从连接池返回的Connection对象,它的close()方法与众不同。调用它的close()方法不是关闭,而是把连接归还给池。

3.4DBCP数据库连接池

DBCP是Apache软件基金组织下的开源连接池实现,要使用DBCP数据源,需要应用程序应在系统中增加如下两个jar文件:

  • Commons-dbcp.jar:连接池的实现
  • Commons-pool.jar:连接池实现的依赖库

Demo:

    public class Demo{
        public static void main(String[] args)
        {
            /*
             *1.创建连接池对象
             *2.配置四大参数
             *3.配置池参数
             *4.得到连接对象
             */
            BasicDataSource dataSource=new BasicDataSource();
            
            dataSource.setDriverClassName(“com.mysql.jdbc.Driver”);
            dataSource.setUrl(“jdbc:mysql://localhost:3306/mydb”);
            dataSource.setUsername(“root”);
            dataSource.setPassword(123);
            
            dataSource.setMaxActive(20);
            dataSource.setMinIdle(3);
            dataSource.setMaxWait(1000);
            
            Connection con=dataSource.getConnection();
            System.out.println(con);
            
          /*
            *连接池内部使用四大参数创建了连接对象,即mysql驱动提供的Connection
            *连接池使用mysql的连接对象进行了装饰,只对close()方法进行了增强!
            *装饰之后的Connection的close()方法,用来把当前连接归还给池
            */
            con.close();//把连接归还给池。
        }
    }

既然谈到装饰,那下面我们就在下文3.7中来谈谈装饰者模式。

3.5c3p0数据库连接池

c3p0,全名叫ComboPooledDataSource;

需要导入的jar包:

  • 连接池的实现:c3p0-0.9.5.2.jar
  • 依赖库:mchange-commons.jar

Demo:

    public class Demo{
        public static void main(String[] args)
        {
            //创建连接池对象
            ComboPooledDataSource dataSource=new ComboPooledDataSource();
            
            //进行四大参数的配置
            dataSource.setDriverClass(“com.mysql.jdbc.Driver”);
            dataSource.setJdbcUrl(“jdbc:mysql://localhost:3306/mydb”);
            dataSource.setUser("root");
            dataSource.setPassword("123");
            
            //池配置
            dataSource.setAcquireIncrement(5);
            dataSource.setInitialPoolSize(20);
            dataSource.setMinPoolSize(2);
            dataSource.setMaxPoolSize(50);
            
            Connection con=dataSource.getConnection();
            System.out.println(con);
            
            con.close();
        }
    }

3.5.1c3p0配置文件的使用

配置文件要求:

文件名称:必须叫c3p0-config.xml。
文件的位置:必须在src下。

c3p0配置文件:
[图片上传失败...(image-7bf99b-1526285770981)]

写入配置文件后的Demo:

    public class Demo{
        public static void main(String[] args)
        {
            //在创建连接池对象时,这个对象就会自动加载配置文件,不用我们来指定。
            ComboPooledDataSource data=new comboPooledDataSource();
            Connection con=data.getConnection();
            System.out.println(con);
        }
    }

3.6Tomcat配置数据库连接池

3.6.1Tomcat配置JNDI资源

JNDI:java命名和目录接口。作用:在服务器上配置资源,然后通过统一的方式来获取配置的资源。

首先需要在Tomcat/conf/Catelina/localhost目录下新建文件名: 项目名.xml

在该.xml文件中写入以下内容


image

3.6.1获取资源的代码

    Context initCtx=new InitialContext();//创建一个上下文。
    
    Context envCtx=(Context) initCtx.lookup(“java:comp/env”);//这个路径是固定的不能改。
    
    MyBean bean=(MyBean)envCtx.lookup(“bean/MyBeanFactory”);//通过该上下文进行二次查找才能找到资源。

Demo:测试类

[图片上传失败...(image-1ef5eb-1526285770981)]

3.7装饰者模式

将对象增强的手段有:

  • 继承

缺点:1.增强的内容是死的,不能动。2.被增强的对象也是死的。

  • 装饰者模式

特点:1.增强的内容是不能修改的。2.被增强的对象可以是任意的。

  • 动态代理(AOP):以后再详讲,博客出来后会给出链接。

下面通过一个简单的例子来对装饰者模式进行讲解

class 咖啡类 {};
class 加奶咖啡 extends 咖啡类 {};
class 加糖咖啡 extends 咖啡类 {};
class 加盐咖啡 extends 咖啡类 {};

咖啡 a=new 加糖咖啡();
咖啡 b=new 加盐咖啡(a);//对a进行装饰,就是给a加盐
咖啡 c=new 加奶咖啡(b);//对b进行装饰,就是给b加奶

装饰者模式在Java API中的IO流中用到的很多。如BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter、ObjectInputStream、ObjectOutputStream这几个都是运用了装饰模式的装饰流。关于的IO流的详情见下篇博客Java之IO流详解

4.ThreadLocal

早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

4.1Thread API

  • void set(Object value);设置当前线程的线程局部变量的值。
  • Object get();该方法返回当前线程所对应的线程局部变量。
  • void remove();将当前线程局部变量的值删除,目的是为了减少内存的占用。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

4.2ThreadLocal内部结构

ThreadLocal内部用Map来保存数据。虽然在使用上述API时没有给出键,但其实它内部使用了当前线程作为键。内部结构见下面demo:

    class ThreadLocal
    {
        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());
        }
    }

5.dbtils结果集处理器介绍

需要导入的jar包:

  • common-dbutil.jar
  • c3p0.jar
  • mchange-commons.jar

关键要得到QueryRunner对象,然后调用其各种方法。

  • update()方法:

1.int update(String sql,Object… params) 可执行增删改语句。
2.重载方法int update(Connection con,String sql, Object… params)需要调用者提供Connection,这说明本方法不再管理Connection了。本重载方法支持事务。

  • query()方法:

1.T query (String sql,ResultSetHandler rsh,Object… params)可执行查询操作。
2.重载方法:T query(Connection con,String sql,ResultSetHandler rsh,Object… params); 本重载方法支持事务。它会先得到ResultSet,然后调用rsh的handle()把rs转换成需要的类型。

  • ResultSetHandler接口

1.BeanHandler(单行)-->构造器需要一个class类型的参数,用来把一行结果转换成指定类型的javaBean对象。
2.BeanListHandler(多行)—>构造器也是需要一个Class类型的参数,用来把一行结果集转换成一个javabean,哪么多行就是转换成List对象,一堆javabean。
3.MapHandler(单行)—>把一行结果集转换成Map对象。
4.MapListHandler(多行)—>把一行记录转换成一个Map,多行就是多个Map,即List<Map>。
5.ScalarHandler(单行单列)->同来用于select count(*)from t_stu语句,结果集是单行单列的,它返回一个Object,就是count(*)的值,为long类型。

dbutil结果处理集原理代码:

    package demo;
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * Created by codingBoy on 16/10/19.
     */
     public class QR<T>
     {
        private DataSource dataSource;

    public QR(DataSource dataSource)
    {
        this.dataSource=dataSource;
    }

    public QR(){
        super();
    }

    public int update(String sql,Object... params)
    {
        Connection con=null;
        PreparedStatement pstmt=null;

        try
        {
            con=dataSource.getConnection();//通过连接池得到连接对象
            pstmt=con.prepareStatement(sql);

            initParams(pstmt,params);//给出参数

            return pstmt.executeUpdate();//调用update执行增、删、该
        }catch (Exception e)
        {
            throw new RuntimeException(e);
        }finally {
            try{
                if (pstmt!=null) pstmt.close();
                if (con!=null) con.close();
            }catch (SQLException e){}
        }
    }

    //给参数赋值
    public void initParams(PreparedStatement pstmt,Object... params) throws SQLException {
        for (int i = 0; i < params.length; i++)
        {
            pstmt.setObject(i+1,params[i]);
        }
    }

    public T query(String sql,RsHandler<T> rh,Object... params) throws SQLException {
        Connection con=null;
        PreparedStatement pstmt=null;
        ResultSet rs=null;

        try
        {
            con=dataSource.getConnection();//通过连接池得到连接对象
            pstmt=con.prepareStatement(sql);

            initParams(pstmt,params);//给出参数

            rs=pstmt.executeQuery();//调用update执行增、删、该

            return rh.handle(rs);
        }catch (Exception e)
        {
            throw new RuntimeException(e);
        }finally {
            
            if (rs!=null) rs.close();
            if (pstmt!=null) pstmt.close();
            if (con!=null) con.close();

        }
    }

    interface RsHandler<T>
    {
        public T handle(ResultSet rs);
    }
    }

这样我们以后对数据库进行增、删、改操作时,只需写以下代码即可:

    1.QueryRunner qr=new QueryRunner(JdbcUtils.getDataSource);//创建QueryRunner对象,并传入连接池对象
    
    2.String sql="insert into user values(?,?,?,?);//给出sql语句模板
    3.Object[] params={参数1,参数2,参数3,参数4};//传入参数
    4.qr.update(sql,params);//调用qr方法。

通过这简单的四步就可以对数据库进行增删改了。

对数据库进行查询操作时,只需写以下代码:

     1.QueryRunner qr=new QueryRunner(JdbcUtils.getDataSource);//创建QueryRunner对象,并传入连接池对象
     2.String sql="select * from user where id=?";//给出sql语句模板
     3. Object[] params={参数};//传入参数
     //4. ResultSetHandler<Object> rsh=new ResultSetHandler(){
     //     @Override
     //     public Object handle(Result rs) throws SQLException{
     //         return null;
     //     }
     // };
     5.Object object=qr.query(sql,new BeanHandler<Object>(Object.class),params);

通过这几步即可实现对数据的查询操作了。

下面的解释写给自己看的:关于connection是否关闭的问题

在jar包中,QueryRunner类的update(没有connection参数的)方法,在finally中将connection进行了关闭;在update(有connection参数的)方法中,在finally中没有对connection进行关闭(暂时这么记吧,不然要是进行关闭了的话,在传智播客写的小工具封装类TxQueryRunner中将connection传入JdbcUtils的releaseConnecion()方法中对connection进行关闭时会出现报错)。

在讲到事务时,我们会对QueryRunner进行再次封装。上述写出的QueryRunner的代码只是包中的QueryRunner源码方法的一部分(因为源码中还有很多的重载方法),我们会通过另一个类TxQueryRunner(较QueryRunner多出的一个功能就是它支持事务)继承该类,在TxQueryrunner类中,对connection进行了判断:若connection为事务中的connection则在TxqueryRunner的update()方法中不对connection进行关闭,而是在commitTransaction()即提交事务时进行关闭;若connection为普通连接,则将connection进行关闭。那么以后我们在DAO中要获取的就不是QueryRunner对象,而是通过QueryRunner qr=new TxQueryRunner();获取TxQueryRunner对象了。

2018.3.19更

欢迎加入我的Java交流1群:659957958。群里目前已有1800人,每天都非常活跃,但为了筛选掉那些不怀好意的朋友进来搞破坏,所以目前入群方式已改成了付费方式,你只需要支付9块钱,即可获取到群文件中的所有干货以及群里面各位前辈们的疑惑解答;为了鼓励良好风气的发展,让每个新人提出的问题都得到解决,所以我将得到的入群收费收入都以红包的形式发放到那些主动给新手们解决疑惑的朋友手中。在这里,我们除了谈技术,还谈生活、谈理想;在这里,我们为你的学习方向指明方向,为你以后的求职道路提供指路明灯;在这里,我们把所有好用的干货都与你分享。还在等什么,快加入我们吧!

2018.4.21更:如果群1已满或者无法加入,请加Java学习交流2群:305335626 。群2作为群1的附属群,除了日常的技术交流、资料分享、学习方向指明外,还会在每年互联网的秋春招时节在群内发布大量的互联网内推方式,话不多说,快上车吧!

6.联系

If you have some questions after you see this article,you can tell your doubts in the comments area or you can find some info by clicking these links.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容