事务{
定义:指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。
数据开启事务的命令:
start transaction——开启事务
roolback——回滚事务
commit——提交事务
如果开启事务,在提交之前出现了异常,就会回滚事务,数据库中的数据就会回滚到开启事务之前的状态
如果开启了事务,但是没有提交,数据库的数据就不会改变。
jdbc控制事务{
}Connection.setAutoCommit(false);
Connection.rollback();
Connection.commit();
例1:
Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try { // 获取连接 conn = JdbcUtils.getConnection(); // 定义sql语句,aaa用户的钱减100,bbb用户的钱加100 String sql1 = "update account set money=money-100 where name='aaa'"; String sql2 = "update account set money=money+100 where name='bbb'"; // 开启事务 conn.setAutoCommit(false); // 预编译sql,执行sql st = conn.prepareStatement(sql1); st.executeUpdate(); int x = 1/0;//这会抛一个异常,这样该程序就无法执行commit语句,自然就无法将数据库中的数据更新。数据还是开启事务之前的数据。如果没有这行代码,程序会正常执行,执行提交方法,修改数据库中的数据 st = conn.prepareStatement(sql2); st.executeUpdate(); // 提交事务 conn.commit(); }finally{ JdbcUtils.release(conn, st, rs); }
例2:(使用到回滚方法)Connection conn = null; PreparedStatement st = null; ResultSet rs = null; Savepoint sp = null; try { // 获取连接 conn = JdbcUtils.getConnection(); // 定义sql语句,aaa用户的钱减100,bbb用户的钱加100 String sql1 = "update account set money=money-100 where name='aaa'"; String sql2 = "update account set money=money+100 where name='bbb'"; // 开启事务 conn.setAutoCommit(false); // 预编译sql,执行sql st = conn.prepareStatement(sql1); st.executeUpdate(); // 如果想让第一条sql语句正常执行就需要定义一个回滚点 sp = conn.setSavepoint(); int x = 1/0; st = conn.prepareStatement(sql2); st.executeUpdate(); // 提交事务 conn.commit(); }catch (Exception e) { e.printStackTrace(); // 进行事务回滚操作,即将数据恢复到回滚点出的数据,就是指执行了第一条sql语句的数据 conn.rollback(sp); // 手动回滚之后一定要提交数据,否则数据库会再次进行回滚到开启事务之前 conn.commit(); }finally{ JdbcUtils.release(conn, st, rs); }
事务的特性(ACID){一个数据库支持事务就必须具有下列特性,如果数据库具有下列特性,就说明该数据库支持事务
原子性(Atomicity):指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
一致性(Consistency):指事务前后数据的完整性必须保持一直。即转账事务开启前,总钱为1000,转账后总钱还应该为1000,类似物理中的能量守恒
隔离性(Isolation):指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务干扰,多个并发事务之前的数据要相互隔离。意思就是:A/B用户都在操作数据库,A用户修改了数据,B用户不能使用A用户修改后的数据持久性(Durability):指一个事务一旦被提交,它对数据库的改变就是永久性的,接下来就算是数据库发生了故障也不能对这个事务修改的数据产生影响。
}事务的隔离级别{
多个事务开启各自的事务操作数据库时,数据库系统就要负责进行隔离操作,保证各个线程在获取数据的准确性;如果不考虑隔离性,会发生以下问题:
脏读:指一个事务读取了另外一个事务未提交的数据。
例:假设A向B转账:1.update account money=money+100 where name='b'; 2.update account money=money-100 where name='a';当第一个sql执行,第二个sql还未执行(A未提交事务),如果此时B查询自己的账户就会发现自己多了100元,这个时候A回滚数据,B就会损失100元。
不可重复度:在一个事务内读取表中的某一行数据,多次读取结果不同(一个事务读取到了另一个事务提交的数据)
例:A用户先查询账户,B用户往A用户转账100元,并且提交了数据。A用户又查一次账户,发现多了100元。
幻读:指一个事务内读取到了别的事务插入的数据,导致前后读取不一致(查表,脏读是查某一行数据)
例:一个用户存钱,但没提交,银行查表发现总共500元,用户提交之后,银行在查表发现多了100元。
}数据库定义的隔离级别(不同的连接对应不同的隔离级别){
Serializable:可避免脏读,不可重复读,幻读
Repeatable read:可避免脏读,不可重复读
Read committed:可避免脏读的发生
Read uncommitted:最低级别,无法避免数据库的命令:
set transaction isolation level + 隔离级别;——设置事务的隔离级别select @@tx_isolation;——查询当前事务的隔离级别
jdbc的使用:
// 获取连接 conn = JdbcUtils.getConnection(); // 设置连接的隔离级别 conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
}
ThreadLocal{
1.在java程序中,为保证事务的准确性,可以使用ThreadLocal
2.这其实是一个存储连接的容器
3.get,set,remove等方法都是操作当前使用的线程的连接,即一个线程都是用同一个连接
4.使用完之后要注意remove连接,否则造成该对象存放太多连接,出现问题
private static DataSource ds = null; static{ try { Properties prop = new Properties(); prop.load(JdbcUtils.class.getClassLoader().getResourceAsStream("dbcp.properties")); ds = BasicDataSourceFactory.createDataSource(prop); } catch (Exception e) { throw new RuntimeException(e); } } // 提供一个ThreadLocal对象,由于需要在静态方法内使用,定义为静态 private static ThreadLocal<Connection> t = new ThreadLocal<Connection>(); public static Connection getConnection(){ try { // 先从容器中获取连接,如果不存在就通过数据源获取连接并存在容器中,如果存在就返回容器中的连接 Connection conn = t.get(); if(conn == null){ conn = ds.getConnection(); t.set(conn); } return conn; } catch (Exception e) { throw new RuntimeException(e); } } public static void startTransaction(){ // 开启事务,先从容器获取连接,没有就创建并存在容器中,然后开启事务 try { Connection conn = t.get(); if(conn == null){ conn = getConnection(); t.set(conn); } conn.setAutoCommit(false); } catch (Exception e) { throw new RuntimeException(e); } } public static void commit(){ // 提交事务,从容器中获取连接,获取到了就提交,没获取说明容器没连接,就不需要提交 try { Connection conn = t.get(); if(conn != null){ conn.commit(); } } catch (Exception e) { throw new RuntimeException(e); } } public static void close(){ // 提供关闭连接的方法,使用完连接之后,必须要关闭连接,同时不管任何情况要保证容器内移除了该链接 try { Connection conn = t.get(); if(conn != null){ conn.close(); } } catch (Exception e) { throw new RuntimeException(e); }finally{ t.remove(); } }
}
}
连接池{
通过应用程序获取连接的缺点:用户的每次请求都需要向数据库获得连接,而数据库创建连接需要消耗相对比较大的资源,创建的时间也就较长。假设网站一天的访问量10万次,那就需要数据库创建10万次连接,极大的浪费了数据库资源,并且极易造成数据库内存溢出,宕机。
定义:就是写一个连接池程序,里面保存了几个连接,当用户需要连接的时候直接从连接池中获取,这样就代替了数据库直接创建连接,大大优化了程序性能。
编写连接池{
步骤:
1.实现java.sql.DataSource接口,实现它内部的两个抽象方法
2.在构造函数中创建数据库的连接,并存放在容器对象中
3.在方法中提供获取容器中连接的方法
4.当用户使用完连接,进行close()连接的时候,该连接会返回给容器对象
// 先要提供容器对象 private static LinkedList<Connection> list = new LinkedList<Connection>(); // 将连接存放在容器中,为保证每次都从一个地方获取连接,可是使用静态代码块 static{ try { // 可以将配置信息放在properties文件中 Class.forName("com.mysql.jdbc.Driver"); for (int i = 0; i < 10; i++) { Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "root"); list.add(conn); } } catch (Exception e) { // 获取连接配置异常 throw new ExceptionInInitializerError(e); } } @Override public Connection getConnection() throws SQLException { // 判断容器中是否有连接 if(list.size()<=0){ throw new RuntimeException("数据库内无连接"); } // 不能使用list.get(index)方法,因为该方法只是取出一个引用,在数据库中并不会移除 // 使用移除第一个的方法,因为这是链表,移除第一个之后,第二个就会前移到第一个 Connection conn = list.removeFirst(); MyConnection my = new MyConnection(conn); return my;//此时调用的都是增强了close方法的connection对象。 } // 为保证用户使用完连接,调用close的方法的时候会返回给连接池,就需要增强close方法: // 可以使用1.写一个connection的子类,覆盖close的方法,然后增强----较少使用 // 2.使用包装设计模式----需要填写过多方法 // 3.用动态代理----最好,aop编程 // 因为第一种方法,需要将connection的信息全部封装到子类中,否则无法实现操作。 // 比如某个方法需要使用连接的信息才能实现,但是子类无法实现该信息,所以无法执行 // 过于麻烦,所以使用包装模式: // 1.实现与需增强的类相同的接口 // 2.将需要增强的对象传到增强类中 // 3.填写增强的方法 // 4.不需要增强的方法,通过调用传递进来的对象的相应方法 class MyConnection implements Connection{ private Connection conn = null; public MyConnection(Connection conn) { super(); this.conn = conn; } @Override public void close() throws SQLException { list.add(this.conn); } @Override public void clearWarnings() throws SQLException { conn.clearWarnings(); } ... }
}
开源连接池{
DBCP数据库连接池{
定义:Apache软件基金组织下的开源连接池实现,使用DBCP连接池,需要在应用程序导入两个库:Commons-dbcp.jar(连接池的实现)和Commons-pool.jar(连接池依赖的数据库)。
1.其实就是开源组织编写了一个dbcp的数据源,通过该数据库源可以获取连接
2.Tomcat的连接池正是采用该连接池实现的
3.该数据库连接池既可以与应用服务器整合使用,也可以由应用程序独立使用
使用{
根据dbcp.properties文件来获取配置信息:
#连接设置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mydb username=root password=root #<!-- 初始化连接 --> dataSource.initialSize=10 #<!-- 最大空闲连接 --> dataSource.maxIdle=20 #<!-- 最小空闲连接 --> dataSource.minIdle=5 #最大连接数量 dataSource.maxActive=50 #是否在自动回收超时连接的时候打印连接的超时错误 dataSource.logAbandoned=true #是否自动回收超时连接 dataSource.removeAbandoned=true #超时时间(以秒数为单位) #设置超时时间有一个要注意的地方,超时时间=现在的时间-程序中创建Connection的时间,如果maxActive比较大,比如超过100,那么removeAbandonedTimeout可以设置长一点比如180,也就是三分钟无响应的连接进行回收,当然应用的不同设置长度也不同。 dataSource.removeAbandonedTimeout=180 #<!-- 超时等待时间以毫秒为单位 --> #maxWait代表当Connection用尽了,多久之后进行回收丢失连接 dataSource.maxWait=1000
获取连接:
// 先将配置文件加载到定义的properties对象中 private static Properties prop = new Properties(); // 定义一个数据源对象,接收创建的dbcp数据源 private static DataSource ds = null; // 由于只需要创建一次dbcp的数据源对象即可,放在静态代码块 static{ //加载配置文件 try { prop.load(OpenPoolDemo.class.getClassLoader().getResourceAsStream("dbcp.properties")); // 获取dbcp的数据源,定义者要求通过工厂模式获取 // 通过prop对象获得数据源 // BasicDataSourceFactory factory = new BasicDataSourceFactory(); // ds = factory.createDataSource(prop); ds = BasicDataSourceFactory.createDataSource(prop); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } // 获取连接的方法 public static Connection getConnection() throws SQLException{ return ds.getConnection(); }
}C3P0数据库连接池{
1.Spring内置的连接池
2.使用mysql数据库需要导入两个库:c3p0-0.9.2.1.jar和mchange-commons-java-0.2.3.4.jar
使用{
手动定义配置信息:
private static ComboPooledDataSource ds = null; // 获取数据源对象,只需要加载一次放在静态代码块/ static{ try { //直接创建数据源对象 ds = new ComboPooledDataSource(); //为ds注册获取数据库连接的配置信息 //1.驱动类 ds.setDriverClass("com.mysql.jdbc.Driver"); //2.要连接的库的位置url ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb"); //3.库的用户名 ds.setUser("root"); //4.库的密码 ds.setPassword("root"); //5.最大允许多少连接 ds.setMaxPoolSize(30); //6.最小允许多少连接 ds.setMinPoolSize(5); //7.初始化连接的个数 ds.setInitialPoolSize(10); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } // 通过数据源获取连接 public static Connection getConnection() throws SQLException{ return ds.getConnection(); }
使用配置文件:
1.文件名必须为c3p0-config.xml
2.必须放在应用的classpath文件下,或WEB-INF/classes文件下(查看c3p0文档的快速入门可知)
配置文件:
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/my</property> <property name="user">root</property> <property name="password">root</property> <property name="initialPoolSize">10</property> <property name="maxIdleTime">30</property> <property name="maxPoolSize">100</property> <property name="minPoolSize">10</property> <property name="maxStatements">200</property> </default-config> <!-- This app is massive! --> <!-- name就是这个配置的名字,调用的时候根据名字获取配置信息 --> <named-config name="halm"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/my</property> <property name="user">root</property> <property name="password">root</property> <property name="acquireIncrement">50</property> <property name="initialPoolSize">100</property> <property name="minPoolSize">50</property> <property name="maxPoolSize">1000</property> <!-- intergalactoApp adopts a different approach to configuring statement caching --> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> </named-config> </c3p0-config>
获取配置文件信息创建连接:private static ComboPooledDataSource ds = null; // 获取数据源对象,只需要加载一次放在静态代码块/ static{ try { // 调用默认的配置信息 ds = new ComboPooledDataSource(); // 调用名字为halm的配置信息 // ds = new ComboPooledDataSource("halm"); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } // 通过数据源获取连接 public static Connection getConnection() throws SQLException{ return ds.getConnection(); }
}
}
}
配置Tomcat数据源,直接从Tomcat中获取连接池{
需要先配置连接池信息(在web应用的配置文件中配置):驱动的.jar文件必须放在服务器的lib下,可以通过Tomcat的文档得知或者(点击打开链接)
说明:<context> <Resource name="jdbc/mydb" auth="Container" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/mydb" maxActive="100" maxIdle="30" maxWait="10000"/> </context>
1.在启动web服务器会创建一个jndi的容器,内部存储着配置的数据源,并且绑定在一个指定的名称上“jdbc/mydb”
2.在程序中就是根据名称来从jndi找到该数据源
}
}
PS:今天参加第一次面试了,以后再将过程贴出来吧