一:帝國之倉-DAO
1.持久層支持
爲什麼需要使用Spring對持久層的支持?
1):原生操作持久層API方式,麻煩.
2):Spring對事務支持非常優秀.
傳統JDBC:
1:代碼臃腫,重複
2:處理異常
3:控制事務
Spring JDBC:
1:簡潔,優雅,簡單
2:運行異常
3:Spring事務管理
![Spring爲什麼對持久層提供技術支持](https://img-blog.csdn.net/20180406231810368?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
![Spring提供的對應模板類](https://img-blog.csdn.net/2018040623194871?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
2.JDBC
需求:使用JDBC完成CRUD操作.
依賴jar:
mysql-connector-java-5.x.11.jar:MySQL驅動包
druid連接池
spring-jdbc-版本.RELEASE.jar:支持JDBC
spring-tx-版本.RELEASE.jar:支持事務
dao
public interface EmployeeDAO {
void save(Employee emp);
void update(Employee emp);
void delete(Long id);
Employee get(Long id);
List<Employee> listAll();
}
impl
public class EmployeeDAOImpl implements EmployeeDAO{
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void save(Employee emp) {
jdbcTemplate.update("INSERT INTO employee (name,age) VALUES (?,?)",emp.getName(),emp.getAge());
}
public void update(Employee emp) {
jdbcTemplate.update("UPDATE employee SET name=? ,age=? WHERE id=?",emp.getName(),emp.getAge(),emp.getId());
}
public void delete(Long id) {
jdbcTemplate.update("DELETE FROM employee where id = ?",id);
}
public Employee get(Long id) {
List<Employee> list = jdbcTemplate.query("SELECT id,name,age eage from employee where id = ?",new Object[]{id},
(rs,rowNum)->{//lanmda表達式其實就是匿名內部類的縮寫,面向行數的編程
Employee e = new Employee();
e.setId(rs.getLong("id"));
e.setName(rs.getString("name"));
e.setAge(rs.getInt("eage"));
return e;
});
return list.size()==1? list.get(0):null;
}
public List<Employee> listAll() {
return jdbcTemplate.query("SELECT id,name,age eage from employee",new Object[]{},new RowMapper<Employee>(){
//把每一行結果集映射成一個Employee對象
public Employee mapRow(ResultSet rs, int rowNum) throws SQLException {
Employee e = new Employee();
e.setId(rs.getLong("id"));
e.setName(rs.getString("name"));
e.setAge(rs.getInt("eage"));
return e;
}});
}
}
App-context.xml
<context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
</bean>
<!-- 配置DAO -->
<bean id="employeeDAO" class="cn.wolfcode.dao.imp.EmployeeDAOImpl">
<property name="dataSource" ref="dataSource" ></property>
</bean>
1:不建議使用QueryForObject因爲得到的結果如果沒有會報錯
2:回顧了一下lambda表達式.
3:解釋了JDBC底層是怎麼處理結果集的
![解釋JDBC底層處理結果集](https://img-blog.csdn.net/20180407004514737?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
Spring JDBC的其他設計
1:給jdbcTemplate的一個基類(父類) 將需要的類繼承與JdbcDaoSupport,但是任然要爲其父類注入DataSource屬性
然後super.getJdbcTemplate().調用的方法
![hibernate等其他類的基類圖](https://img-blog.csdn.net/20180407012449666?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
2:將參數用NamedParameterJdbcTemplate來設置
NamedParameterJdbcTemplate namedParameterJdbcTemplate = null;
namedParameterJdbcTemplate.update("INSERT INTO employee (name,age) VALUES (:ename,:eage)",new HashMap(){{
this.put("ename",emp.getName());
this.put("eage", emp.getAge());
}});
![具體參數設置細節](https://img-blog.csdn.net/20180407012153524?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
二:帝國之盾-TX
1.引出事務
dao
![根據銀行轉賬案例引出事務](https://img-blog.csdn.net/20180407122553180?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
public class AccountDAOImpl implements IAccountDAO{
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void transOut(Long outId, int money) {
jdbcTemplate.update("UPDATE account set balance = balance - ? where id = ?",money,outId);
}
public void transIn(Long inId, int money) {
jdbcTemplate.update("UPDATE account set balance = balance + ? where id = ?",money,inId);
}
}
service
public class AccountServiceImpl implements IAccountService{
private IAccountDAO dao;
public void setDao(IAccountDAO dao) {
this.dao = dao;
}
public void trans(Long outId, Long inId, int money) {
dao.transOut(outId, money);
int i = 3 / 0;
dao.transIn(inId, money);
}
}
App
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class App {
@Autowired
private IAccountService service;
@Test
public void testTrans() throws Exception {
service.trans(10086L,10010L,1000);
}
}
2.事務回顧
![何爲數據庫事務](https://img-blog.csdn.net/20180407122703713?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
數據庫併發問題:
併發問題類型 產生原因和效果
第一類丟失更新 兩個事務更新相同數據,如果一個事務提交,另一個事務回滾,第一個事務的更新會被回滾
髒讀 第二個事務查詢到第一個事務未提交的跟新數據,第二個事務根據該數據執行,但第一個事務回滾第二個事務操作髒數據
虛讀 一個事務查詢到另一個事務已經提交的新數據,導致導致多次查詢數據不一致
不可重複讀 一個事務查詢到另一個事務已經修改的數據,導致多次查詢數據不一致
第二類丟失數據 多個事務同時讀取相同數據,並完成各自的事務提交,導致最後一個事務提交會覆蓋前面所有事務對數據的改變
![事務的隔離級別](https://img-blog.csdn.net/20180407141043712?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
默認情況下MySQL不會出現幻讀.除非:select * from 表名 lock in share mode;
MySQL中鎖基於索引機制,也不會出現第一類丟失更新.
Oracle支持 READ COMMITED(缺省) 和 SERIALIZABLE.
MySQL支持 四種隔離級別, 缺省爲REPEATABLE READ.
隔離級別如何選用:
隔離界別越高,數據庫事務併發執行性能越差,能處理的操作越少.
因此在實際項目開發中爲了考慮併發性能一般使用READ COMMITED.它能避免丟失跟新和髒讀儘管不可
重複讀和幻讀不能避免,更多的情況下使用悲觀鎖或樂觀鎖來解決.
事務的類型:
本地事務:就是普通事務,能保證單臺數據庫上的操作ACID,被限定在一臺數據庫上.
分佈式事務:涉及多個數據庫源的事務,即跨越多臺同類或異類數據庫的事務(由每臺數據庫的本地事務組成的),
分佈式事務旨在保證這些本地事務的所有操作ACID,使事務可以跨越多臺數據庫;
JDBC事務和JTA事務:
JDBC事務:就是數據庫事務類型中的本地事務,通過Conenction對象的控制來管理事務;
JTA事務:JTA指(Java Transaction API).是Java EE數據庫事務規範,JTA只提供了事務管理接口,由應用
程序服務器廠商提供實現,JTA事務比JDBC更強大,支持分佈式事務.
按是否通過編程實現事務有聲明式事務和編程式事務:
編程式事務:通過編寫代碼來管理事務.
聲明式事務:通過註解或XML配置來管理事務.
3.Spring對事務支持的API
PlatformTransactionManager:根據TransactionDefinition提供的事務屬性配置信息,創建事務.
平臺的事務管理器,是多種事務管理器的基類,覆蓋了處理事務的方法.
public interface PlatformTransactionManager {
//根據事務定義的環境中返回一個已經存在的事務,如果不存在,則新建一個.
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
//提交事務
void commit(TransactionStatus status) throws TransactionException;
//回滾事務
void rollback(TransactionStatus status) throws TransactionException;
}
TransactionDefinition:封裝事務的隔離級別和超時時間,是否爲只讀事務和傳播規則等事務屬性.
TransactionStatus:封裝了事務的具體運行狀態,如是否新開啓事務,是否已經提交事務,設置當前事務爲rollback-only等.
它們三者之間有關聯的.
Hibernate : HibernateTransactionManager
JDBC/MyBatis: DataSourceTransactionManager
![如何正確的使用JDBC/MyBatis等連接池](https://img-blog.csdn.net/20180407145436503?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
4.事務的傳播規則
![事務的傳播行爲](https://img-blog.csdn.net/20180407151009598?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
寄生事務並不是所有的事務管理器都支持,比如HibernateTransactionManager默認就不支持,需要手動去開啓
JDBC和MyBatis的事務管理器:DataSourceTransactionManager:默認就是支持的.
5.事務配置
![<tx:method name=""/>裏面的屬性配置](https://img-blog.csdn.net/20180407160349699?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
通過XML的方式配置事務的屬性
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
" >
<context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
</bean>
<!-- 配置DAO -->
<bean id="accountDAOImpl" class="cn.wolfcode.dao.imp.AccountDAOImpl">
<property name="dataSource" ref="dataSource" ></property>
</bean>
<!-- 配置Service -->
<bean id="accountServiceImpl" class="cn.wolfcode.service.impl.AccountServiceImpl">
<property name="dao" ref="accountDAOImpl"></property>
</bean>
<!-- ============================================================================= -->
<!-- 1:WHAT:配置JDBC事務管理器 -->
<!-- 記得拷貝TX事務這個包 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2:WHEN:配置事務管理器增強 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="trans"/>
</tx:attributes>
</tx:advice>
<!-- 3:WHERE:配置一個切面 -->
<aop:config>
<aop:pointcut expression="execution(* cn.wolfcode.service.*Service.*(..))" id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
<!-- ============================================================================= -->
<!-- 配置一個CRUD的通用事務的配置 -->
<tx:advice id="crudAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- service中的查詢方法 -->
<tx:method name="get*" read-only="true" propagation="REQUIRED"/>
<tx:method name="list" read-only="true" propagation="REQUIRED"/>
<tx:method name="query*" read-only="true" propagation="REQUIRED"/>
<!-- service中其他方法(非查詢) -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
</beans>
6.使用註解配置
domain
@Data
public class Account {
private Long id;
private int balance;
}
dao
public interface IAccountDAO {
/**
* 從指定賬戶轉出多少錢
* @param outId
* @param money
*/
void transOut(Long outId,int money);
/**
* 從指定賬戶轉入多少錢
* @param inId
* @param money
*/
void transIn(Long inId,int money);
}
impl
@Repository
public class AccountDAOImpl implements IAccountDAO{
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void transOut(Long outId, int money) {
jdbcTemplate.update("UPDATE account set balance = balance - ? where id = ?",money,outId);
}
public void transIn(Long inId, int money) {
jdbcTemplate.update("UPDATE account set balance = balance + ? where id = ?",money,inId);
}
}
service
public interface IAccountService {
/**
* 從指定賬戶轉出指定金額給另一個賬戶
* @param outId
* @param inId
* @param money
*/
void trans(Long outId,Long inId,int money);
}
impl
@Service
@Transactional
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDAO dao;
public void trans(Long outId, Long inId, int money) {
dao.transOut(outId, money);
int i = 3 / 0;
dao.transIn(inId, money);
}
@Transactional(readOnly=true)
public void list(){
}
}
App
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class App {
@Autowired
private IAccountService service;
@Test
public void testTrans() throws Exception {
service.trans(10086L,10010L,1000);
}
}
App-Context.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
" >
<!-- DI註解解析器 -->
<context:annotation-config/>
<!-- IoC註解解釋器 -->
<context:component-scan base-package="cn.wolfcode"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- TX註解解釋器 -->
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
<!-- 從classpath的根路徑去加載db.properties文件 -->
<context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
</bean>
</beans>
1:切記在xml文件中配置 DataSourceTransactionManager <bean>屬性因爲它是JDBC/MyBatis專屬事務
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
2:記得配置 TX 註解解析器
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
3:@Transactional()給需要使用事務的類配置,配置到類上表示類下面的所有方法都使用事務,()可以設置事務的屬性
@Transactional()配置到某個方法上,設置的屬性,就覆蓋所有類的事務屬性,就可以單獨設置,比如給查詢方法設置只讀
@Transactional(readOnly=true)
public void list(){
}
6.使用Java-Config來做配置
service
//其餘跟上面代碼一樣
@Service
@Transactional
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDAO dao;
public void trans(Long outId, Long inId, int money) {
dao.transOut(outId, money);
//int i = 3 / 0;
dao.transIn(inId, money);
}
}
AppConfig
//當前項目的配置類,好比是applicationContext.xml
@Configuration //標誌當前類爲一個配置類
@Import(DataSourceConfig.class)//包含其他配置類
@ComponentScan //IoC註解解析器 DI註解默認引入進來 不寫的話從當前的包,包括子包都要掃描
@EnableTransactionManagement//事務註解解析器
public class AppConfig {
//創建事務管理的Bean
@Bean
public DataSourceTransactionManager txManager(DataSource ds){
return new DataSourceTransactionManager(ds);
}
}
DataSourceConfig
//當前項目的連接池的配置類
@Configuration
@PropertySource("classpath:db.properties")
public class DataSourceConfig {
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.initialSize}")
private int initialSize;
//創建連接池的Bean
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driverClassName);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
ds.setInitialSize(initialSize);
return ds;
}
}
App
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=AppConfig.class)
public class App {
@Autowired
private IAccountService service;
@Test
public void testTrans() throws Exception {
service.trans(10086L,10010L,1000);
}
}