JavaWeb学习笔记之Jdbc(二)

1.事务

1.1事务的四大特性(ACID)

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

1.2mysql中操做事务

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

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

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

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;ide

也能够经过下面命令来设置隔离级别:set transaction isolationlevel[4选1];工具

2.4在Jdbc中设置隔离级别

con.setTransactionisolation[int lever];性能

3.数据库链接池

用户每次请求都须要向数据库得到连接,而数据库建立链接一般须要消耗相对较大的资源,建立时间也较长。假设网站一天10万访问量,数据库服务器就须要建立10万次链接,极大的浪费数据库的资源,而且极易形成数据库服务器内存溢出、拓机。以下图所示:

数据库链接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤其突出.。对数据库链接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库链接池正式针对这个问题提出来的。数据库链接池负责分配,管理和释放数据库链接,它容许应用程序重复使用一个现有的数据库链接,而不是从新创建一个。以下图所示:

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

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

  • 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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配置文件:

写入配置文件后的Demo:

1
2
3
4
5
6
7
8
9
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文件中写入如下内容

3.6.1获取资源的代码

1
2
3
4
5
Context initCtx=new InitialContext();//建立一个上下文。

Context envCtx=(Context) initCtx.lookup(“java:comp/env”);//这个路径是固定的不能改。

MyBean bean=(MyBean)envCtx.lookup(“bean/MyBeanFactory”);//经过该上下文进行二次查找才能找到资源。

Demo:测试类

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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。
    5.ScalarHandler(单行单列)->同来用于select count(*)from t_stu语句,结果集是单行单列的,它返回一个Object,就是count(*)的值,为long类型。

dbutil结果处理集原理代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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
2
3
4
5
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
2
3
4
5
6
7
8
9
10
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对象了。