JDBC以及事务连接池的使用---学习笔记

JDBC

JDBC简介

JDBC(java database connectivity,java数据库连接)
可以直接通过java语言操作数据库

jdbc是一套标准,它是由一些接口与类组成的的

涉及到的类和接口
	主要再两个包下面
		java.sql
			类:DriverManger
			接口:Connection Statement ResultSet PreparedStatment 
			CallableStatement(它是用于调用存储过程)
		javax.sql
			接口:DataSource

什么是驱动?

两个设备要进行通信,满足一定通信数据格式,数据格式由设别提供商规定,设备提供商为设备提供驱动软件

通过软件可以与改设备进行通信

JDBC入门

编写一个jdbc入门代码,完成对数据库的操作

create table USER(
	id int primary key auto_increment,
	username varchar(20) unique not null,
	password varchar(20) not null,
	email varchar(40) not null
);

desc USER;

select * from USER;

insert into USER values(null,'tom','123','ffdfsa@gg.com');
insert into USER values(null,'fox','123','ffdfsa@gg.com');

1.下载jar包
	将驱动的jar包放在lib下
2.创建一个jdbcdemo类
		//1.注册驱动
		DriverManager.registerDriver(new Driver());
		
		//2。获取连接对象
		Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/day17", "root", "password");
		
		//3.通过连接对象获取sql语句Statement
		Statement st = con.createStatement();
		
		//4.操作sql语句
		String sql = "select * from user";
		
		//操作select语句,会返回一个ResultSet结果集
	    ResultSet rs = st.executeQuery(sql);
	    
	    //5.遍历结果集
//	    boolean flag = rs.next();		//向下移动,返回值为true,代表有下一条记录
//		int id = rs.getInt(1);
//		
//		String username = rs.getString(2);			//rs.getString("username");
//		System.out.println(id);
//		System.out.println(username);
	    
	    while(rs.next()){
	    	int id = rs.getInt("id");
	    	String username = rs.getString("username");
	    	String password = rs.getString("password");
	    	String email = rs.getString("email");
	    	
	    	System.out.println(id+"   "+username+"   "+password+"   "+email);
	    }
	    
	    //6.释放资源
	    rs.close();
	    st.close();
	    con.close();

DirverManager对象

DriverManager.registerDriver(new Driver());
//加载了两个驱动(com.mysql.jdbc.Driver里面有个静态代码块给加载了一下,
我们自己new Driver()又加载了)


Class.forName("com.mysql.jdbc.Driver");	//反射,只是加载一次驱动

用反射的的好处:
	1.只加载一次,装入一个驱动对象
	2.降低耦合,不依赖于驱动

通过DriverManager来获取连接对象

Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/day17", "root", "password");

	url的作用,就是用于确定一个驱动
		mysql url:jdc:mysql://localhost:3306/数据库名
		oralce url:jdbc:oracle:thin:@localhost:1521:sid

DriverManager作用

1.注册驱动
2.获取连接Connection

关于url

url格式

主协议	子协议	主机			端口		数据库
jdbc:	mysql:	//localhost:3306/day17

mysql的url可以简写
	前提:主机是localhost 端口是3306
	jdbc:mysql///day17

url后面可以加参数   如:useUnicode=true

Connection详情

java.sql.Connection,它代表一个连接对象。就是程序于数据库的连接

Connection作用:
	1.可以通过Connection获取操作sql的Statement对象
		Statement createStatement() throws SQLException
		
	2.操作事务
		setAutoCommit(boolean flag);开启事务
		rollback();事务回滚
		commit();提交事务

Statement详解

java.sql.Statement用于执行sql语句
Statement作用:
	1.执行sql
		DML
			int executeUpdate(String sql)
			利用返回值判断非0来确定sql语句是否执行成功
			
		DQL
			ResultSet executeQuery(String sql)

		可以通过execute方法来执行任何sql语句

	2.批处理操作

		addBatch(String sql);		将sql语句添加到批处理	
		executeBatch();		批量执行
		clearBatch();		清楚批处理

ResultSet详解

java.sql.ResultSet他是用于封装select语句执行后的的结果

常用API:
	1.next()方法
		判断是否有下一条记录,如果有返回true,游标下移一行
	2.getXxx()仿佛获取当前游标指向这条记录的数据
		getInt()
		getString()
		getDate()
		getDouble()....
		参数有两种
			1.getInt(int columnIndex)
			2.getInt(String columnName)

		如果列的类型不知到,可以通过下面的方法
			getObject()

关闭资源

try...catch...
finally{
		//6.释放资源
		try {
			if (rs != null) {
			rs.close();
			}
		} catch (SQLException e) {
			
			e.printStackTrace();
		}
			
		
		try {
			if(st != null)
				st.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			if(con!=null)
				con.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

JDBC示例—CURD

1.查询
	1.查询全部
	2.条件查询
2.修改
3.删除
4.添加

关于JdbcUtils抽取
	只抽取到Connection
	public static Connection getConnection() throws ClassNotFoundException, SQLException{
		//1.注册驱动
		Class.forName("com.mysql.jdbc.Driver");
		//2.获取连接
		Connection con = DriverManager.getConnection("jdbc:mysql:///day17","root","w1xd_RQC");
		return con;
	}

上述抽取的缺点
	1.它只能针对于mysql数据库
	2.每一次调用,都会注册一次驱动

改进
	1.将关于连接数据库的信息定义到配置文件中
		读取配置文件的时候加载

private static final String DRIVERCLASS;
private static final String URL;
private static final String USERNAME;
private static final String PASSWORD;

static{
	DRIVERCLASS = ResourceBundle.getBundle("jdbc").getString("driverClass");
	URL = ResourceBundle.getBundle("jdbc").getString("url");
	USERNAME = ResourceBundle.getBundle("jdbc").getString("username");
	PASSWORD = ResourceBundle.getBundle("jdbc").getString("password");
}

static{
	//1.注册驱动,静态代码块只加载一次
	try {
		Class.forName(DRIVERCLASS);
	} catch (ClassNotFoundException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

public static Connection getConnection() throws ClassNotFoundException, SQLException{
	
	//2.获取连接
	Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
	return con;
}

//关闭操作
public static void closeConnection(Connection con) throws SQLException{
	if(con!=null){
		con.close();
	}
}

滚动结果集

默认得到的ResultSet它只能向下遍历(next()),对于ResultSet可以设置成滚动的,可以向上遍历,或者直接定位到一个指定的物理行号

问题:怎么得到一个滚动结果集?

设置参数 
Statement st = con.createStatement(
					ResultSet.TYPE_SCROLL_INSENSITIVE,
					ResultSet.CONCUR_UPDATABLE
				);


参数 int type

ResultSet.TYPE_FORWORD_ONLY 结果集的游标只能向下滚动。

ResultSet.TYPE_SCROLL_INSENSITIVE 结果集的游标可以上下移动,当数据库变化时,当前结果集不变。

ResultSet.TYPE_SCROLL_SENSITIVE 返回可滚动的结果集,当数据库变化时,当前结果集同步改变。

参数 int concurrency

ResultSet.CONCUR_READ_ONLY 不能用结果集更新数据库中的表。

ResultSet.CONCUR_UPDATETABLE 能用结果集更新数据库中的表。

dao模式

使用dao模式完成登陆操作
	1.web层
		login.jsp	LoginServlet	User
	2.Service层
		UserService
	3.dao层
		UserDao

sql注入

由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数中添加一些sql关键字,达到改变sql运行结果的目的,也可以完成恶意攻击

示例:
	在输入用户名时 fox' or '1'='1
	此时就不会验证密码了
解决方案:
	PreparedStatement
	他是一个预设Statement,是java.sql.Statement接口的一个子接口

总结PreparedStatement使用:
	1.在sql语句中,使用"?"占位
		String sql = "select * from user where username=? and password=?";
	2.得到PreparedStatement对象
		PreparedStatement pst = con.prepareStatement(String sql);
	3.对占位符复制
		pst.SetXxx(int index,Object obj);
		index从1开始,占位符的序号
	4.执行sql
		DML pst.executeUpdate();
		DQL pst.executeQuery();			
		无参数,因为sql已经被预处理了

	他的优点:
		1.解决sql注入(具有预处理功能)
		2.不需要再拼sql语句

jdbc处理大数据

mysql中有大数据

blob大二进制
	TINYBOLB(255),BLOB(64kb),MEDIUMBLOB(16m),LONGBLOB(4g)
text(clob)大文本
	TINYTEXT(255),TEXT(64kb),MEDIUMTEXT(16m),LONGTEXT(4g)


大数据操作,一般只有insert select

演示:
	create table myblob{
		id int primary key auto_increment,
		content longblob
	}

向表中插入数据

	mysql驱动不支持setBinaryStream(int,InputStream);

	mysql支持pst.setBinaryStream(1,fis,file.length())也会出错,因为fis.length()是long类型

	因该改为pst.setBinaryStream(1,fis,(int)file.length())
	

	如果文件比较大,修改my.ini配置文件

jdbc批处理

一次可以执行多条sql语句

再jdbc中可执行sql语句的对象有Statment,PrepareStatement,他们都提供批处理

1.Statment执行批处理
	st.addBatch();	将sql语句添加到批处理
	st.executeBatch();	执行批处理
	st.clearBatch();
1.PreparedStatment执行批处理
	pst.addBatch();	将sql语句添加到批处理
	pst.executeBatch();	执行批处理
	pst.clearBatch();清空缓存

以上两个对象执行批处理的区别?
	1.Statement更适合执行不同sql的批处理,它没有预处理功能,性能比较低
	2.PreparedStatement它时候执行相同sql的批处理,它提供了预处理功能

		mysql默认情况下,批处理中的预处理功能没有开启,需要开启
			1.在url下添加参数
				url=jdbc:mysql:///day17?useServerPrepStmts=true&cachePreStmts=true&rewriteBatcheStatements=true
			2.注意驱动版本
				5.1.13以上

事务

事务是什么,有什么用?

事务就是一个事情,组成这件事有多个单元,这些单元要么全成功,要么都不成功。保证数据的完整性

事务怎样操作

创建表
	create table account(
		id int primary key auto_increment,
		name varchar(20),
		money double
	);

insert into account values(null,'aaa',1000);

insert into account values(null,'bbb',1000);

insert into account values(null,'ccc',1000);

1.mysql下怎么操作
	方式1:
		start transaction	开启事务
		rollback	事务回滚
		commit	事务提交

	方式2:
		show variables like '%commit%';	可以查看当前autocommit值
			在mysql数据库中它的默认值是"on"代表自动事务
			自动事务的意义就是:执行任意一条sql语句都会自动提交事务

			测试:将autocommit设置为off(oracle默认是off)
				set autocommit=off;手动commit才能提交
2.jdbc下怎么操作
	java.sql.Connection接口中有几个方法可以操作事务

		1.setAutocommit(boolean flag)
			如果flag=false;相当于start transaction;
		2.rollBack()
			事务回滚
		3.commit()	提交

	String sql = "update account set money=500 where id=2";
	
	Connection con = null;
	Statement st = null;
	
	try {
		con = JdbcUtils.getConnection();
		con.setAutoCommit(false); //开启事务start transaction
		st = con.createStatement();
		st.executeUpdate(sql);
	} catch (Exception e) {
		// TODO: handle exception
		//事务回滚
		con.rollback();
	}finally{
		try {
			con.commit(); //事务提交
			st.close();
			con.close();
		} catch (Exception e2) {
			// TODO: handle exception
		}
	}


	回滚放在catch中

事务特性(重点) ACID

原子性
一致性
隔离性:事物之间不能相互影响
持久性:一个事务一旦提价哦,对数据库数据的改变是永久的

如果不考虑事务的隔离性,会出现什么问题?

	1.脏读   一个事务读取到另一个事务的未提交数据
	2.不可重复读
		两次读取的数据不一致(update)
	3.虚读(幻读)
		两次读取的数据不一致(insert)
	4.丢失更新
		两个事务对同一条记录进行操作,后提交的事务,将先提交的事务的修改覆盖了

演示以上问题,以及解决方案
	对于以上问题,我们可以通过设置事务的隔离级别来解决
	1.事务的隔离级别有哪些?
		Serializable:可避免脏读,不可重复读,幻读情况的发生(串行化)
		Repeatable read:可避免脏读,不可重复读
		Read committed:可避免脏读情况发生(读已提交)
		Read uncommitted:最低级别,以上均无法保证 
	2.怎么设置事务的隔离级别?
		1.mysql中的设置
			1.查看事务的隔离级别
				select @@tx_isolation 查询当前事务的隔离级别
				mysql默认是:REPEATABLE-READ
				oracle默认是:Read committed
			2.mysql中怎么设置事务隔离级别
				set session transaction isolation level	设置事务隔离级别
		2.jdbc的设置
			在jdbc中设置事务隔离级别
			使用java.sql.Connection接口中提供的方法
			void setTransactionIsolation(int level) throw SQLException
			参数可取:
				Connection.TRANSACTION_READ_UNCOMMITTED
Connection.TRANSACTION_READ_COMMITTED
Connection.TRANSACTION_REPEATABL_READ
Connection.TRANSACTION_SERIALIZABLE

	3.演示
		1.脏读
			一个事务读取到另一个事务的未提交数据
			设置A,B事务隔离级别为 read uncommitted
				set session transaction isolation level read uncommitted;
			1.在A事务中
				start transaction;
				update account set money=money-500 where name='aaa';
				update account set money=money+500 where name='bbb';
			2.在B事务中
				start transaction;
				select * from account;

			这时,B事务读取时,会发现,钱已近汇完。那么就出现了脏读
		
			当A事务提交前,执行rollbak,再提交,B事务在查询,就会看发现,钱恢复成原样了
			也就出现了两次查询结果不一致问题,出现了不可重复读

		2.解决脏读问题
			将事务的隔离级别设置为read committed;
			

			将A,B的事务隔离级别都设置为read committed
				set session transaction isolation level read committed;

			1.在A事务中
				start transaction;
				update account set money=money-500 where name='aaa';
				update account set money=money+500 where name='bbb';
			2.在B事务中
				start transaction;
				select * from account;

			A事务提交后,数据才变,解决了脏读但现在B事务再查询,数据不一致了,没有解决不可重复度

		3.解决不可重复读

			将事务的隔离级别设置为	Repeatable read来解决不可重复读
			设置A,B事务隔离级别为 Repeatable read;
			set session transaction isolation level repeatable read;

			步骤同上
			当A事务提交后,B事务查询,于上次查询结果一致

		4.设置事务隔离级别为 Serializable,它可以解决所有问题
			set session transaction isolation level Serializable;
			会出现锁表,也就是说,一个事务在对表操作时,其他事务操作不了

案例:转账汇款----使用事务

问题:service调用了dao中两个方法完成了一个业务操作,如果其中一个方法执行失败怎样办?
	需要事务控制
	
问题:怎样进行事务控制?
	我们在service层进行事务的开启,回滚以及提交操作。
	
问题:进行事务操作需要使用Connection对象,那么,怎样保证,在service中与dao中所使用的是同一个Connection.
	在service层创建出Connection对象,将这个对象传递到dao层.
	
注意:Connecton对象使用完成后,在service层的finally中关闭	
	 而每一个PreparedStatement它们在dao层的方法中用完就关闭.
	 
关于程序问题
	1.对于转入与转出操作,我们需要判断是否成功,如果失败了,可以通过抛出自定义异常在servlet中判断,
	  进行信息展示 。
	 
----------------------------------------------------------
问题:
	在设置dao层时,
	
		public interface AccountDao {
			public void accountOut(String accountOut, double money) throws Exception;

			public void accountIn(String accountIn, double money) throws Exception;

		}
	那么我们自己去实现这个接口时,怎样处理,同一个Connection对象问题?
		使用ThreadLocal
		
		ThreadLocal可以理解成是一个Map集合
		Map<Thread,Object>
		set方法是向ThreadLocal中存储数据,那么当前的key值就是当前线程对象.
		get方法是从ThreadLocal中获取数据,它是根据当前线程对象来获取值。
		
		如果我们是在同一个线程中,只要在任意的一个位置存储了数据,在其它位置上,就可以获取到这个数据。
		
	关于JdbcUtils中使用ThreadLocal
		1.声明一个ThreadLocal
			private static final ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
		2.在getConnection()方法中操作
			Connection con = tl.get(); 直接从ThreadLocal中获取,第一次返回的是null.
			if (con == null) {
				// 2.获取连接
				con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
				tl.set(con); //将con装入到ThreadLocal中。
			}

丢失更新

多个事务对同一条记录进行了操作,后提交的事务将先提交的事务更新了

怎么解决丢失更新问题

1.悲观锁
	悲观锁 (假设丢失更新一定会发生 ) ----- 利用数据库内部锁机制,管理事务
		1.共享锁
			select * from table lock in share mode(读锁、共享锁)
		2.排它锁
			select * from table for update (写锁、排它锁)

摘抄的一段:对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据,对于排他锁大家的理解可能就有些差别,我当初就犯了一个错误,以为排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制

update语句默认添加排它锁

2.乐观锁
	乐观锁 (假设丢失更新不会发生)------- 采用程序中添加版本字段解决丢失更新问题

	create table product (
			id int,
			name varchar(20),
			updatetime timestamp
	);

	insert into product values(1,'冰箱',null);
	update product set name='洗衣机' where id = 1;

连接池(数据源)

连接池是什么?有什么用?

连接池:创建一个容器,可以装入多个Connection对象,在使用连接对象时,从容器中获取一个Connection,使用后,重新装入连接池(DataSource)

连接池获取Connection对象
优点:节省创建连接与释放连接 性能消耗 ---- 连接池中连接起到复用的作用 ,提高程序性能	

自动连接池

1.创建一个MyDataSource类,在这个类中创建一个LinkedList<Connection>
2.在其构造方法中初始化List集合,并向其中装入5个Connection对象。
3.创建一个public Connection getConnection();从List集合中获取一个连接对象返回.
4.创建一个  public void readd(Connection) 这个方法是将使用完成后的Connection对象重新装入到List集合中.

一些问题

	1.创建连接池有标准

		在javax.sql下有DataSource接口
		连接池必须实现DataSource

	自定义的连接池必须实现接口

	2.我们操作时,要使用标准,怎样可以让con.close()不是销毁,而是将其重新装入到连接池

		要解决这个问题,其本质是将Connection 的close()方法行为进行改变
		
		三种改变行为的方法
			1.继承
			2.装饰模式
				1.装饰类于被装饰类要实现同一个接口或继承同一个父类
				2.在装饰类中持有一个被装饰引用
				3.对方法进行功能增强
			3.动态代理

	 Proxy.newProxyInstance(con.getClass().getClassLoader(),
		 con.getClass().getInterfaces(), new InvocationHandler() {
		
		@Override
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			if ("close".equals(method.getName())) {
				//将连接对象,重新装入到集合中
				System.out.println("重新将con对象装入到连接池");
				ll.add(con);
				
				return null;
			}else{
				return method.invoke(con, args);		//如果不是close方法,执行原来操作
			}
		}
	});
				
	Connection对象如果从连接池获取到,close方法的行为已经改变,不是销毁,
	而是重新装入到连接池中	

1.连接池必须实现javax.sql.DataSource接口。
2.要通过连接池获取连接对象  DataSource接口中有一个  getConnection方法.
3.将Connection重新装入到连接池   使用Connection的close()方法。

开源连接池

1.dbcp(了解)
	dbcp是apache的一个开源连接池。
	需要导包commons-dbcp-1.4.jar,commons-pool-1.5.6.pool

	使用:
		1.手动配置,编写
	BasicDataSource bds = new BasicDataSource();
	
	//需要设置连接数据库最基本的条件4个
	bds.setDriverClassName("com.mysql.jdbc.Driver");
	bds.setUrl("jdbc:mysql:///day18");
	bds.setUsername("root");
	bds.setPassword("pssword");
	
	//得到一个Connection
	Connection con = bds.getConnection();
		2.自动配置,用配置文件
	FileInputStream fis = new FileInputStream("E:\\javaweb\\day18_2\\src\\dbcp.properties");
	props.load(fis );
	
	DataSource ds  = BasicDataSourceFactory.createDataSource(props );
2.c3p0(重要)
	C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等
	需要导包,c3p0-0.9.1.2.jar

	使用
		1.手动
	ComboPooledDataSource cpds = new ComboPooledDataSource();
	
	cpds.setDriverClass("com.mysql.jdbc.Dirver");
	cpds.setJdbcUrl("jdbc:mysql:///day18");
	cpds.setUser("root");
	cpds.setPassword("password");

		2.自动(配置文件)
			c3p0的配置文件可以是properties,XML

			c3p0的配置文件如果名称叫做c3p0.properties or c3p0-config.xml
			 并且放置在classpath路径下(对于web应用就是classes目录)
			那么c3p0会自动查找

	使用:
	ComboPooledDataSource cpds = new ComboPooledDataSource();
		
	//底层会自动查找c3p0-config.xml或者c3p0.properties
		
			注意:我们其实只需要把配置文件放置在src下就可以


3.区别
c3p0与dbcp区别
dbcp没有自动回收空闲连接的功能
c3p0有自动回收空闲连接功能	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值