数据库事务
原子性: 多个操作组成最小不可分割单元。
一致性: 操作成功后,数据库的状态和业务规则是一致的。
隔离性: 并发数据处理时,相互不干扰。
持久性: 一旦事物提交成功后,数据的操作被记入库中。
数据库并发问题
脏读:读取不可靠的数据(数据被回滚了),Oracle不会发生脏读情况。
不可重复读:同一事务中两次读取数据有差异(数据被别的事务修改、删除)。
幻象读:A事务读到B事务提交的新数据,导致A前后读取不一致。(很像不可重复读,不同的是幻象第二次数据是增加。采用锁表防止增加。)
第一次丢失更新:A事务撤销时,在A事务开始和结束的B事务也抹杀了。无视B的存在。
第二次丢失更新:A事务覆盖B事务已提交的数据,造成B事务操作丢失。
事务隔离级别
隔离级别 | 脏 读 | 不可重复读 | 幻象读 | 第一类丢失更新 | 第二类丢失更新 |
---|---|---|---|---|---|
READ UNCOMMITED | 允许 | 允许 | 允许 | 不允许 | 允许 |
READ COMMITTED | 不允许 | 允许 | 允许 | 不允许 | 允许 |
REPEATABLE READ | 不允许 | 不允许 | 允许 | 不允许 | 不允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 | 不允许 | 不允许 |
JDBC+事务
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
//JDBC的事务
public class JDBC_Demo {
public static void main(String[]args) throws SQLException {
Connection conn = null;
try {
//获取数据连接...
conn = DriverManager.getConnection("");
//关闭自动提交事务
conn.setAutoCommit(false);
//设置事务隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_NONE);
Statement stmt = conn.createStatement();
int rows = stmt.executeUpdate("insert into user u values(1,'大黑')");
//提交事务
conn.commit();
} catch (Exception e) {
// 回滚事务
conn.rollback();
}finally {
//......
}
}
}
Spring+事务
Spring用到了ThreadLocal和单例来保证线程安全
数据源配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.1.xsd">
<!-- DBCP数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db_database17"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- C3P0数据源 -->
<bean id="mySource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"
p:driverClass="oracle.jdbc.driver.OracleDriver"
p:jdbcUrl="jdbc:oracle:thin:@localhost:1521:ora9i"
p:use="admin"
p:password="123456" />
</beans>
Spring事务管理器实现类 => DataSourceTransactionManager
DataSourceTransactionManager:可管理JDBC和MyBatis的事务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.1.xsd">
<!-- DBCP数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db_database17"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- 引用DBCP数据源,基于DBCP数据源管理事务。 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
</beans>
事务同步管理器
Spring将JDBC的Connection、Hibernate的Session等访问数据库的链接、会话对象称为资源,这些资源同一时刻不线程共享。Spring用ThreadLocal为每个线程做了独立的副本,同时维护事务配置的属性和运行状态信息。事务同步管理器是Spring事务管理的基石。
事务传播行为
说白了就是类与类间的调用。Spring通过事务传播行为控制当前事务如何传播到被调用的方法中。
事务传播行为类型 | 说 明 |
---|---|
PROPAGATION_REQUIRED | 如果没有当前事务,则新建一个事务;如果已存在一个事务,则加入这个事务中。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,以非事务方式运行。 |
PROPAGATION_MANDATORY | 使用当前事务。当前没有事务,抛异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务。如果当前存在事务,则把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式操作。如果当前存在事务,则把事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行。如果当前存在事务,则抛异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则嵌套事务内运行;当前无事务,则新建事务。 |
使用XML声明事务
使用原始的TransactionProxyFactoryBean代理类对业务类进行代理
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class BbtForum {
public interface ForumDao {}
public interface TopicDao{}
public interface PostDao{}
public ForumDao forumDao;
public TopicDao toipDao;
public PostDao postDao;
public void addTopic() {
System.out.println("添加");
}
public void getForum() {
System.out.println("查找");
}
}
<!-- DBCP数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db_database17"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- 引用DBCP数据源,基于DBCP数据源管理事务。 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<!-- 需要实施事务增强的目标Bean -->
<bean id="bbtForumTarget" class="com.nan.BbtForum"
p:forumDao-ref="forumDao"
p:topicDao-ref="topicDao"
p:postDao-ref="postDao" />
<!-- 使用事务代理工厂类为目标业务Bean提供事务增强 -->
<bean id="bbtForum"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
p:transactionManager-ref="transactionManager"
p:target-ref="bbtForumTarget">
<!-- 事务属性配置 -->
<property name="transactionAttributes">
<props>
<!-- 只读事务 -->
<prop key="get*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
基于AOP/tx命名空间配置事务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<!-- DBCP数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db_database17"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- 引用DBCP数据源,基于DBCP数据源管理事务。 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<!-- 使用切点表达式语言定义目标方法 -->
<aop:config>
<!-- 通过AOP定义事务增强切面 -->
<aop:pointcut id="serviceMethod" expression="execution(com.jun.service.Forum.*(..))"/>
<!-- 引用事务增强 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" />
</aop:config>
<!-- 事务增强 -->
<tx:advice id="txAdvice" transaction-manager ="txManager" >
<!-- 事务属性定义 -->
<tx:attributes>
<tx:method name="get*" read-only="false" />
<tx:method name="add*" rollback-for="Exception"/>
<tx:method name="update*"/>
</tx:attributes>
</tx:advice>
</beans>
使用注解配置声明事务
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class BbtForum_Demo {
@Transactional(value="topic",readOnly=true)
public void addTopic() {
System.out.println("添加");
}
@Transactional(value="forum")
public void fetForum() {
System.out.println("查询");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring/context/4.1.xsd">
<!-- 扫描包 -->
<context:component-scan base-package="com.nan"/>
<!-- DBCP数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db_database17"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- 引用DBCP数据源,基于DBCP数据源管理事务。 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<!-- 对标注@Transactional注解的Bean进行加工处理,以织入事务管理切面 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
通过AspectJ LTW引入事务切面
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring/context/4.1.xsd">
<!-- DBCP数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db_database17"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- 引用DBCP数据源,基于DBCP数据源管理事务。 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<!-- 扫描包 -->
<context:component-scan base-package="com.nan"/>
<tx:annotation-driven/>
</beans>
JDBC
JDBC访问数据库的过程
- 加载驱动,建立连接。
- 创建语句对象。
- 执行SQL语句。
- 处理结果集。
- 关闭连接。
JDBC中一些接口
- 驱动管理接口DriverManager
- 连接接口 Connection、DatabasemetaData
- 语句对象接口 Statement 、PreparedStatement 、CallableStatement
- 结果集接口 ResultSet 、ResultSetMetaData
Driver接口及驱动类加载
要使用JDBC接口,需要先将对应数据库的实现部分(驱动)加载进来。 驱动类加载方式(Oracle)装载驱动类,驱动类通过static块实现在DriverManager中的“自动注册”
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection接口
Connection接口负责应用程序对数据库的连接,在加载驱动之后,使用url、username、password三个参数,创建到具体数据库的连接。Class.forName("oracle.jdbc.OracleDriver")。
根据url连接参数,找到与之匹配的Driver对象,调用其方法获取连接
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.0.26:1521:tarena","name","123456");
Statement接口
//Statement接口用来处理发送到数据库的SQL语句对象,通过Connection对象创建。常用三个方法:
Statement stmt=conn.createStatement();
//1.execute方法,如果执行的sql是查询语句且有结果集则返回true,如果是非查询语句或者没有结果集,返回false
boolean flag = stmt.execute(sql);
//2.执行查询语句,返回结果集
ResultSetrs = stmt.executeQuery(sql);
//3.执行DML语句,返回影响的记录数
int flag = stmt.executeUpdate(sql);
ResultSet接口
查询的结果存放在ResultSet对象的一系列行中,指针的最初位置在行首,使用next()方法用来在行间移动,getXXX()方法用来取得字段的内容。ResultSet代表DQL查询结果,其内部维护了一个读取数据的游标,默认情况在,游标在第一行数据之前,
当调用next()方法时候,游标会向下移动,并将返回结果集中是否包含数据, 如果包含数据就返回true。
结果集还提供了很好getXXX方法用于获取结果集游标指向当前行数据。
Demo
public static void main(String[] args) throws Exception{
//注册驱动
String driver="oracle.jdbc.OracleDriver";
Class.forName(driver);
//连接数据库
String url="jdbc:oracle:thin:@196.168.201.227:2221:orcl";
String user="name";
String pwd="123456";
Connection conn=DriverManager.getConnection(url, user, pwd);
//创建Statement
Statement st=conn.createStatement();
//执行SQL
String sql="select id, name from user_";
ResultSet rs=st.executeQuery(sql);
//处理结果
//rs结果集中包含一个游标,游标默认在结果集的第一行之前
//rs.next():移动结果集游标到下一行,检查是否有数据, 如果有返回true, 否则false
while(rs.next()){
//getXXX(列名): 返回结果集当前行中
// 指定列名的数据.
int id = rs.getInt("id");
String name=rs.getString("name");
//输出查询结果
System.out.println(id+","+name);
}
//关闭连接
conn.close();
}