事务
一、事务的隔离级别
一、事务的隔离级别
数据库系统要负责隔离操作,写代码只需要对隔离级别进行设置。
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没有关系,就不能得到数据源