Spring 框架 DAO 與 事務 的總結

一:帝國之倉-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默認就不支持,需要手動去開啓

    JDBCMyBatis的事務管理器: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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章