访问数据库基本是所有java web项目必备的,不论是oracle、mysql,或者是nosql,肯定需要和数据库打交道。一开始学java的时候,肯定是以jdbc为基础,如下:
private static int insert(Student student) {
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/samp_db";
String username = "root";
String password = "";
Connection conn = null;
int i = 0;
String sql = "insert into students (Name,Sex,Age) values(?,?,?)";
PreparedStatement pstmt;
try {
Class.forName(driver); //classLoader,加载对应驱动
conn = (Connection) DriverManager.getConnection(url, username, password);
pstmt = (PreparedStatement) conn.prepareStatement(sql);
pstmt.setString(1, student.getName());
pstmt.setString(2, student.getSex());
pstmt.setString(3, student.getAge());
i = pstmt.executeUpdate();
pstmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
} return i;
}
spring对dao层提供了不同的模板类,主要如下;
主要机制如下:
数据源
在spring中,数据连接是通过数据源获得的。数据源一般是由web应用服务器提供,spring中,通过jndi的方式获取,也可以直接在spring容器中配置,当然也可以通过代码的方式创建一个数据源。
1、dbcp数据源
jar包依赖:
commons-dbcp2-2.1.1.jar
commons-logging-1.2.jar
commons-pool2-2.4.2.jar
配置如下:
2、c3p0数据源
jar包依赖:
mchange-commons-java-0.2.11.jar
c3p0-0.9.5.2.jar
配置如下:
3、jndi数据源
如果应用配置在高性能的应用服务器上,那么可能希望使用应用服务器本身提供的数据源。应用服务器的数据源使用JNDI开放调用者使用,spring专门提供了引用jndi数据源的JndiObjectFactoryBean,配置如下:
另外spring为获取java ee资源提供了一个jee命名空间,通过jee可以有效的简化java ee资源的引用。利用jee命名空间引用jndi数据源的配置如下:
4、DriverManagerDataSource(spring提供)
事务
针对事务,本人准备在后续数据库章节专门进行阐述,这里只要知道事务是为了保证一系列的操作要么同时成功,要么同时失败的机制。
jdbc事务操作:
在jdbc3.0之后,引入了savepoint的概念,流程如下;
spring对事务的支持
spring事务管理的亮点在于声明式事务管理,主要包括如下几个元素:
1、TransactionDefinition
定义了spring兼容的事务属性,包括事务隔离、事务传播、事务超时、只读状态等等。
2、TransactionStatus
代表一个事务的具体运行状态
3、PlatformTransactionManagerspring
提供的事务管理的对象
spring可以支持很多orm框架,例如mybatis、jpa、hibernate等,spring提供的事务管理类如下:
spring事务管理器实现类
1、spring jdbc、mybatis
基于数据源的connection访问数据库,所以可以使用DataSourceTransactionManager,配置如下:
DataSourceTransactionManager可以使用DataSource的connection对象的commit()、rollback()等方法来管理事务。
2、jpa
jpa通过javax.persistence.EntityTransaction来管理jpa事务。
其中EntityTransaction需要通过javax.persistence.EntityManager#getTransaction()获得,EntityManager则是由javax.persistence.EntityManagerFactory#createEntityManager()获取。
事务同步管理器
在spring中,jdbc的connection,hibernate的session等访问数据库的连接或会话对象统称为资源,这些资源在同一时刻是不能多线程共享的。那么如何解决呢?spring使用事务同步管理器(org.springframework.transaction.support.TransactionSynchronizationManager)使用ThreadLocal为不同事务线程提供了独立的资源副本,同时维护事务配置的属性和运行状态信息。
spring事务配置
1、xml配置方式
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<tx:method name="save*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!-- 切面 -->
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* com.jeenotes.ssm.service.*.*(..))" />
</aop:config>
</beans>
不过这种方式一看就不方便,下面说说注解方式
2、基于注解配置(@Transactional)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启事务控制的注解支持,配置 Annotation 驱动,扫描@Transactional注解的类定义事务。proxy-target-class为true代表使用CGLIB类代理;false则代表使用的是jdk动态代理 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
</beans>
public class DefaultFooService implements FooService { public Foo getFoo(String fooName) {
// do something
} //方法上注解属性会覆盖类注解上的相同属性
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// do something
}
}
@Transactional注解:
a、属性
b、作用范围
@Transactional注解可以应用于接口定义、接口方法、类定义和类的public方法上。
但是不建议使用在接口中,主要是因为当proxy-target-class="true"时,接口的实现类将会工作在非事务环境下,所以spring建议在具体的类上使用@Transactional。
c、方法中使用@Transactional
@Transactional(readOnly = true)
public class DefaultFooService implements FooService { public Foo getFoo(String fooName) {
// do something
} // these settings have precedence for this method
//方法上注解属性会覆盖类注解上的相同属性
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// do something
}
}
spring事务易错点
1、关于spring事务,经常会有一种错误的说法,就是一个事务方法不应该调用另一个事务方法,否则将产生两个事务。
事务的传播行为
spring中的默认事务传播行为是PROPAGATION_REQUIRED,因此当一个方法使用@Transactional修饰,调用另一个使用@Transactional修饰的方法,则这两个方法其实是工作在同一个事务当中。
2、多线程下的事务安全
web容器基本都是多线程的,即使开发者认为自己没有开启多线程,但其实整个web容器为了能够处理更多的请求,都是使用共享线程池的方式来处理请求。在spring应用中,大部分的bean都是单例的,因此倒不会出现线程安全问题。
但是dao层持有的都是connection对象,这个连接对象肯定是状态化的。spring的解决方式就是通过ThreadLocal将有状态的变量(connection等)本地线程化,实现线程安全。
结论:在相同线程中进行相互嵌套调用的事务方法工作在相同的事务中;如果这些相互嵌套调用的方法工作在不同的线程中,则不同线程下的事务方法工作在独立的事务中。
3、哪些方法实现不了spring aop事务
大家都知道aop的实现原理主要有jdk动态代理或者cglib,那么对于spring aop事务的实现,还是有一定的要求的:
- 基于jdk动态代理的aop事务基于接口,所以要求方法必须是public修饰,并且不能使用static修饰;
- 而基于cglib字节码动态代理的方案是通过扩展被增强类,动态创建其子类的方式进行aop增强,但是final、static、private修饰的方法都不能被子类覆盖,所以这些方法无法实施aop增强。