SSH綜合練習(倉庫管理系統)、事務傳播機制(部分)

1.開發前準備

1.1 原型(靜態頁面)導入

idea和eclipse中目錄結構是不同,idea中的web目錄和eclipse中webRoot是一個意思
在這裏插入圖片描述


1.2 數據庫生成

使用mysql數據庫(sql文本在資料中)


2.搭建項目環境

2.1 引入ssh的jar和相關配置

(1)在WEB-INF中創建lib文件夾,並複製jar包
(2)設置依賴此lib包
在這裏插入圖片描述


2.1 Web.xml以及其他配置文件

(1)web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<display-name></display-name>
	<!-- Spring ServletContextListener -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- 加載配置文件默認 WEB-INF/applicationContext.xml -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>

	<!-- Struts2 核心 過濾器 -->
	<filter>
		<filter-name>struts2</filter-name>
		<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
</web-app>
  • ContextLoaderListener:
    在ServletContext初始化的時候,加載spring核心配置(applicationContext.xml),因爲默認加載的位置是WEB-INF/applicationContext.xml,所以需要重新設置全局配置
  • StrutsPrepareAndExecuteFilter:
    Struts2核心過濾器,入口

在項目下創建resources文件夾,並設置爲resources(等於是一個權限),在resources中新建我們需要的配置文件
在這裏插入圖片描述
(2)log4j.properties文件

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=info, stdout

(3)struts.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>
	<!-- 
		開發者模式:關閉:副作用:如果是ajax請求的情況下,一旦出錯,不會在頁面上打印錯誤 
		所以採用struts.i18n.reload和struts.configuration.xml.reload的模式
	-->
    <!--   <constant name="struts.devMode" value="true" /> -->
    <!-- 不用重啓服務器 -->
	<constant name="struts.configuration.xml.reload" value="true" />
	<!-- 表單樣式 -->
	<constant name="struts.ui.theme" value="simple" />

    <package name="default" namespace="/" extends="struts-default">
  
    </package>

</struts>

(4)applicationContext.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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:cache="http://www.springframework.org/schema/cache"
       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
                           http://www.springframework.org/schema/cache
                           http://www.springframework.org/schema/cache/spring-cache.xsd">
	<!-- 引入外部屬性文件 -->
	<context:property-placeholder location="classpath:db.properties" />

	<!-- 連接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="${jdbc.driverClass}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- sessionFactory -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<!-- 第一部分: 連接池 -->
		<property name="dataSource" ref="dataSource" />
		<!-- 第二部分: hibernate常用屬性 -->
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
				<prop key="hibernate.hbm2ddl.auto">update</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.format_sql">true</prop>
			</props>
		</property>
		<!-- 第三部分: 引入hbm -->
		<!-- <property name="mappingResources">
			<list>
				<value>cn/itcast/storemanager/domain/Goods.hbm.xml</value>
				<value>cn/itcast/storemanager/domain/History.hbm.xml</value>
				<value>cn/itcast/storemanager/domain/Store.hbm.xml</value>
				<value>cn/itcast/storemanager/domain/Userinfo.hbm.xml</value>
			</list>
		</property> -->
		<!-- 可以批量引入hbm
		支持通配符
		 -->
		<property name="mappingLocations">
			<list>
				<value>classpath:cn/itcast/storemanager/domain/*.hbm.xml</value>
			</list>
		</property>
	</bean>
	
	<!-- 配置事務 -->
	<!-- 配置事務管理器平臺 -->
	<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>
	<!-- 事務管理通知 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="save*" read-only="false"/>
			<tx:method name="update*" read-only="false"/>
			<tx:method name="delete*" read-only="false"/>
			<tx:method name="find*" read-only="true"/>
		</tx:attributes>
	</tx:advice>
	
	<!-- 切入點和切面 -->
	<aop:config>
		<aop:pointcut expression="bean(*Service)" id="txPointcut"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
	</aop:config>
	
</beans>

擴展1:ComboPooledDataSource(c3p0連接池配置)

spring內置了一個數據源DriverManagerDataSource,在spring-jdbc-4.2.4.RELEASE.jar包下

<!-- 配置內置的數據源bean -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
	<property name="url" value="jdbc:mysql:///itcastspring"/>
	<property name="username" value="root"/>
	<property name="password" value="root"/>
</bean>

<!-- jdbctemplate對象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<!-- 注入數據源 -->
	<property name="dataSource" ref="dataSource"/>
</bean>

但這個數據源不建議生成環境下使用,我們使用c3p0的數據源ComboPooledDataSource,在c3p0-0.9.2.1.jar

<!-- c3p0連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="com.mysql.jdbc.Driver"/>
	<property name="jdbcUrl" value="jdbc:mysql:///itcastspring"/>
	<property name="user" value="root"/>
	<property name="password" value="root"/>
</bean>

<!-- jdbctemplate對象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<!-- 注入數據源 -->
	<property name="dataSource" ref="dataSource"/>
</bean>

這裏的JdbcTemplate是一個模板工具類,簡化jdbc編程(詳情可參考spring第三天 spring jdbcTemplate的使用)

我們這裏可以使用Spring的junit測試
(可以參考Spring第二天 spring的junit測試)

作用:可以簡化測試代碼,直接讀取spring的核心配置,手動初始化spring容器
通過兩個註解:

  • @RunWith(SpringJUnit4ClassRunner.class)
  • @ContextConfiguration(locations=“classpath:applicationContext.xml”)

其中@RunWith是junit-4.12.jar包下的,其他SpringJUnit4ClassRunner和@ContextConfiguration是spring-test-4.2.4.RELEASE.jar包下

//目標:測試一下spring的bean的某些功能
@RunWith(SpringJUnit4ClassRunner.class) //junit整合spring的測試//立馬開啓了spring的註解
@ContextConfiguration(locations="classpath:applicationContext.xml") //加載核心配置文件,自動構建spring容器
public class SpringTest {

    //注入要測試bean
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void testCreatetable(){
        jdbcTemplate.execute("create table test004(id int,name varchar(20))");
    }
}

但是我們這邊不使用JdbcTemplate


擴展2:LocalSessionFactoryBean

spring爲不同的ORM持久化技術,提供了不同的支持

ORM持久化技術 模板類
JDBC org.springframework.jdbc.core.JdbcTemplate
Hibernate5.0 org.springframework.orm.hibernate5.HibernateTemplate
IBatis(MyBatis) org.springframework.orm.ibatis.SqlMapClientTemplate
JPA org.springframework.orm.jpa.JpaTemplate

因爲我們已經學了Hibernate,所以我們這邊使用spring對Hibernate的支持
Hibernate的核心是SessionFactory,並由SessionFactory創建Session,再由Session操作數據庫。

public class HibernateUtil {
    private static SessionFactory factory;
    // 使用靜態代碼塊獲取SessionFactory
    static {
        //細節:Hibernate把所有可預見的異常,都轉成了運行時異常(工具中不會提示要添加異常塊)
        try {
            // 加載hibernate.cfg.xml配置文件
            Configuration config = new Configuration().configure();
            factory = config.buildSessionFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取一個新的Session對象(每次都是一個新對象)
     * @return
     */
    public static Session openSession(){
        return factory.openSession();
    }

    /**
     * 獲取一個新的Session對象(每次都是一個新對象)
     * @return
     */
    public static Session getCurrentSession(){
        return factory.getCurrentSession();
    }
}

我們現在可以通過spring-orm包提供LocalSessionFactoryBean實現
spring整合Hibernate的方式有兩種

(1)方式一:直接在spring中加載hibernate配置文件 (零障礙整合)

<!-- spring整合Hibernate:將session工廠交給spring管理,這是spring整合hibernate的核心 -->
<!-- 方式一: 直接在spring中加載hibernate配置文件 (零障礙整合) -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
	
	<!-- 注入Hibernate的核心配置文件
	提示技巧:xxxLocation,一般都要加classpath -->
	<property name="configLocation" value="classpath:hibernate.cfg.xml"/>
</bean>

原理:Web.xml 配置監聽器 —> 初始化spring容器 —> 初始化hibernate配置—>初始化所有單例對象 —> sessionFactory 建表
容器之間的關係原理:
在這裏插入圖片描述
(2)方式一:(推薦)將hibernate參數配置到spring文件
沒有hibernate.cfg.xml配置文件 !!!完全由spring進行管理

<!-- 方式二: (推薦)將hibernate參數配置到spring文件  -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
	<!-- 1.數據源 -->
	<property name="dataSource" ref="dataSource"/>
	<!-- 2.Hibernate的屬性 -->
	<property name="hibernateProperties">
		<props>
		<!-- 設置方言 -->
			<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
			<!-- 設置打印sql語句 -->
			<prop key="hibernate.show_sql">true</prop>
			<!-- 格式化sql語句 -->
			<prop key="hibernate.format_sql">true</prop>
			<!-- 自動建表 -->
			<prop key="hibernate.hbm2ddl.auto">update</prop>
		</props>
	</property>		
	<!-- 3.mapping映射 -->
	<property name="mappingResources">
		<list>
			<value>cn/itcast/ssh/domain/Book.hbm.xml</value>
		</list>
	</property>
</bean>

測試代碼

//目標:測試一下spring的bean的某些功能
@RunWith(SpringJUnit4ClassRunner.class) //junit整合spring的測試//立馬開啓了spring的註解
@ContextConfiguration(locations="classpath:applicationContext.xml") //加載核心配置文件,自動構建spring容器
public class SpringTest {

    @Autowired
    private SessionFactory sessionFactory;

    @Test
    public void testCreatetable(){
        Session session = sessionFactory.openSession();
        System.out.println(session);
        //開啓事務
        Transaction tx =session.beginTransaction();
        //獲取Query對象
        Query query =session.createQuery("from Book order by id desc");
        //調用Query對象的方法,獲取結果集
        List list = query.list();
        //遍歷結果集
        for(Object obj : list){
            System.out.println(obj);
        }
        tx.commit();//提交事務
    }
}

在執行的過程中出現了兩個異常

  • Caused by: java.lang.ClassNotFoundException: org.hibernate.engine.FilterDefinition
    改爲org.springframework.orm.hibernate5.LocalSessionFactoryBean 即可
  • 如果使用sessionFactory.getCurrentSession()獲取session會報Could not obtain transaction-synchronized Session for current thread
    可參考原因及解決方案

Spring 整合 Hibernate,提供 HibernateTemplate 簡化原始Hibernate代碼 (不用關心session,你只需要用就行了)
HibernateDaoSupport 是spring-orm-4.2.4.RELEASE.jar下的包

public class BookDaoImpl extends HibernateDaoSupport {
	
	//保存圖書
	public void save(Book book){
		//注入sesson工廠,獲取session--不會寫了
		//因爲:spring提供了模版類,來整合Hibernate
		super.getHibernateTemplate().save(book);
	}
	//更新圖書(根據id)
	public void update(Book book){
		super.getHibernateTemplate().update(book);
	}
	//刪除圖書(根據id)
	public void delete(Book book){
		super.getHibernateTemplate().delete(book);
	}
	
	//根據id查詢
	public Book findById(Integer id){
		return super.getHibernateTemplate().get(Book.class, id);
	}
	
	//查詢所有
	public List<Book> findAll(){
		return super.getHibernateTemplate().loadAll(Book.class);
//		return super.getHibernateTemplate().find("from Book");//hql方式
	}
	
	//複雜條件條件
	//1.命名查詢:QBN
	public List<Book> findByNamedQuery(String queryName, Object... values){
		return super.getHibernateTemplate().findByNamedQuery(queryName, values);
	}
	
	//2.離線條件查詢:QBC
	public List<Book> findByCriteria(DetachedCriteria criteria){
		return super.getHibernateTemplate().findByCriteria(criteria);
	}
}

applicationContext.xml

<!-- dao -->
<bean id="bookDao" class="cn.itcast.dao.BookDao">
    <!-- 注入sessionFactory,這是在Dao層使用hibernateTemplate的條件,用來操作數據庫的crud -->
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

測試:

@RunWith(SpringJUnit4ClassRunner.class) //junit整合spring的測試//立馬開啓了spring的註解
@ContextConfiguration(locations="classpath:applicationContext.xml") //加載核心配置文件,自動構建spring容器
public class SpringTest {

    @Autowired
    private SessionFactory sessionFactory;

    @Autowired
    private BookDao bookDao;

    @Test
    public void testHibernate(){
        bookDao.save(new Book(null, "金瓶梅", 16d)); //報異常
        // Book book = bookDao.findById(1);	//可執行
    }
}

我們會發現還是會報錯 org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.

這是因爲如果我們不對這個sessionFactory配置事務,默認只能執行查詢的操作(所以在執行增傷改時報異常)


擴展3:配置事務

可參考(spring第四天)

  • 配置事務管理器平臺
  • 事務管理通知
  • 事務的切入點和切面配置

(1)配置事務管理器平臺 HibernateTransactionManager
因爲用了hibernate5.LocalSessionFactoryBean,所以這裏的事務管理器也要改成hibernate5下的事務管理器
org.springframework.orm.hibernate5.HibernateTransactionManager

Spring爲不同的持久化框架提供了不同PlatformTransactionManager接口實現:

事務 說明
org.springframework.jdbc.datasource.DataSourceTransactionManager 使用Spring JDBC或iBatis 進行持久化數據時使用
org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate5.0版本進行持久化數據時使用
org.springframework.orm.jpa.JpaTransactionManager 使用JPA進行持久化時使用
org.springframework.jdo.JdoTransactionManager 當持久化機制是Jdo時使用
org.springframework.transaction.jta.JtaTransactionManager 使用一個JTA實現來管理事務,在一個事務跨越多個資源時必須使用
  • DataSourceTransactionManager針對JdbcTemplate、MyBatis 事務控制 ,使用Connection(連接)進行事務控制 :
    開啓事務 connection.setAutoCommit(false);
    提交事務 connection.commit();
    回滾事務 connection.rollback();
  • HibernateTransactionManager針對Hibernate框架進行事務管理,使用Session的Transaction相關操作進行事務控制 :
    開啓事務 session.beginTransaction();
    提交事務 session.getTransaction().commit();
    回滾事務 session.getTransaction().rollback()

事務管理器的選擇?用戶根據選擇和使用的持久層技術,來選擇對應的事務管理器。
此處我們使用了Hibernate作爲持久化技術

(2)事務管理通知
spring支持兩種事務管理方式

  • 編程式事務管理
    要修改代碼,所以不常用
  • 聲明式事務管理
    使用XML或註解配置聲明式事務
    Spring的聲明式事務是通過AOP實現的(環繞通知)

聲明式事務也有兩種實現方式

  • XML配置方式添加事務管理(tx、aop元素)

  • 註解配置方式添加事務管理@Transactional

瞭解(spring第四天)

實現方式一:XML配置方式添加事務管理`(tx、aop元素)

<!-- 第一步:定義具體的平臺事務管理器(DataSource事務管理器) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<!-- 注入數據源 -->
	<property name="dataSource" ref="dataSource"/>
</bean>
   <!-- 第二步:定義通知,通知中要處理的就是事務 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<!-- 配置事務的屬性定義 -->
	<tx:attributes>
		<!-- 配置具體的方法的事務屬性
		isolation//事務的隔離級別,默認是按數據庫的隔離級別來
		propagation//事務的傳播行爲,默認是同一個事務
		timeout="-1":事務的超時時間,默認值使用數據庫的超時時間。
		read-only="false":事務是否只讀,默認可讀寫。
		rollback-for:遇到哪些異常就回滾,其他的都不回滾
		no-rollback-for:遇到哪些異常不回滾,其他的都回滾。和上面互斥的
		 -->
		<tx:method name="transfer" isolation="DEFAULT"	propagation="REQUIRED" timeout="-1" read-only="false"/>
		
		<!-- 支持通配符
			要求service中 方法名字必須符合下面的規則
		 -->
		<tx:method name="save*"/>
		<tx:method name="update*"/>
		<tx:method name="delete*"/>
		<tx:method name="find*" read-only="true"/>
	</tx:attributes>
</tx:advice>
<!-- 第三步:配置切入點,讓通知關聯切入點,即事務控制業務層的方法 -->
<aop:config>
	<!-- 切入點 -->
	<aop:pointcut expression="bean(*Service)" id="txPointcut"/>
	<!-- 切面 -->
	<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

<!-- dao -->
<bean id="accountDao" class="cn.itcast.spring.dao.AccountDaoImpl">
	<!-- 注入數據源,才擁有jdbctemplate -->
	<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 業務層 -->
<bean id="accountService" class="cn.itcast.spring.service.AccountServiceImpl">
	<!-- 注入dao -->
	<property name="accountDao" ref="accountDao"/>
</bean>

實現方式二:註解配置方式添加事務管理 @Transactional
步驟:
1.在需要管理事務的方法或者類上面 添加@Transactional 註解
2.配置註解驅動事務管理(事務管理註解生效的作用)(需要配置對特定持久層框架使用的事務管理器)

在applicationContext.xml中開啓事務註解驅動

<!-- 第一步:定義具體的平臺事務管理器(DataSource事務管理器) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 注入數據源 -->
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置事務註解驅動 :識別事務的註解@tr。。。
	transaction-manager:具體的平臺事務管理器
-->
<!-- <tx:annotation-driven transaction-manager="transactionManager"/> -->
<!-- 默認的平臺事務管理器的名字叫:transactionManager,此時transaction-manager="transactionManager"可以不寫 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

在需要添加事務管理的方法或類上添加@Transactional

public class AccountServiceImpl implements IAccountService {

    //注入dao
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Transactional(readOnly=false)
    @Override
    public void transfer(String outName, String inName, Double money) {
        //調用dao層
        //先取出
        accountDao.out(outName, money);
        //再轉入
        int i = 1/0;
        accountDao.in(inName, money);
    }
}

測試

@Autowired
private IAccountService accountService;

@Test
public void test1(){
    accountService.transfer("Tom", "Jack", 1000d);
    System.out.println("轉賬成功!");
}

配置事務的定義屬性信息,在註解中直接配置:
在這裏插入圖片描述

事務的傳播行爲:
測試:

public class UserDao extends JdbcDaoSupport {

	@Autowired
    private RoleDao roleDao;

	@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = Exception.class)
    public void testTranstation() throws Exception {
        String sql = "insert into user values(?,?)";
        super.getJdbcTemplate().update(sql, "wxf", "wxf");
        // roleDao對象調用方法
        roleDao.testTranstationREQUIRES_NEW();// user回滾 role不回滾
        // 異常
        int a = 1 / 0;
    }
}

public class RoleDao extends JdbcDaoSupport {

	@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT, rollbackFor = RuntimeException.class)
    public void testTranstationREQUIRES_NEW() throws RuntimeException, SQLException {
        String sql = "insert into role values(?,?)";
        super.getJdbcTemplate().update(sql, "test", "test");
    }
}
@Autowired
private UserDao userDao;

@Test
public void test() throws Exception {
    userDao.testTranstation();
}

執行結果如下:user回滾 role不回滾

原理:

  1. 當UserDao中有@Transactional註解,spring容器在創建對象時,掃描到此註解,就會通過動態代理創建代理對象
    此處的userDao是一個代理對象,它的類型是cn.itcast.dao.UserDao$$EnhancerBySpringCGLIB$$aaf591c2,是一個通過cglib代理對象
    當我們刪除UserDao中的@Transactional註解,創建的就是一個class cn.itcast.dao.UserDao
  2. 所以我們在執行testTranstation()實際上執行的是代理對象的方法(通過AOP進行增強,此處的增強是被事務管理)
  3. 在SpringUnit中,我們注入的userDao是代理對象,同時userDao中的roleDao也是代理對象,(兩個對象都被spring管理,spring掃描到有@Transactional註解,就創建了代理對象),所以userDao.testTranstation()執行的是代理對象的方法(AOP增強,此處的增強就是被事務管理),roleDao.testTranstationREQUIRES_NEW()同理
  4. REQUIRED(支持當前事務,如果不存在 就新建一個),REQUIRES_NEW (如果有事務存在,掛起當前事務,創建一個新的事務),所以testTranstationREQUIRES_NEW() 方法有自己的事務,當 testTranstation()方法遇到異常,只回滾自己事務中對數據庫的操作

在這裏插入圖片描述

此時我們再做一個實驗,把testTranstationREQUIRES_NEW()方法放到UserDao類中(同一個類中事務方法調用事務方法)

public class UserDao extends JdbcDaoSupport {
	@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = Exception.class)
    public void testTranstation() throws Exception {
        String sql = "insert into user values(?,?)";
        int update = super.getJdbcTemplate().update(sql, "wxf", "wxf");
        // 調用同類的方法
        testTranstationREQUIRES_NEW();
        // 異常
        int a = 1 / 0;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT, rollbackFor = RuntimeException.class)
    public void testTranstationREQUIRES_NEW() throws RuntimeException {
        String sql = "insert into role values(?,?)";
        int update = super.getJdbcTemplate().update(sql, "test", "test");
    }
}
@Autowired
private UserDao userDao;

@Test
public void test() throws Exception {
    userDao.testTranstation();
}

執行結果如下:user回滾 role回滾

原理:

  1. 此處的userDao依舊是代理對象,在執行userDao.testTranstation(),我們看一下其中的this的類型,我們發現thisclass cn.itcast.dao.UserDao 類型,this就是被代理的對象,我們可以回過頭看下AOP的動態代理
  2. 既然this不是代理對象,所以執行this.testTranstationREQUIRES_NEW()也就不會被aop增強
    在這裏插入圖片描述

簡而言之:

  • 在同一個類中,一個方法調用另外一個有註解(比如@Async,@Transational)的方法,註解是不會生效的。

問:JDBCTemple底層是使用了connection,那麼每一個dao中的JDBCTemple使用的connection是否是同一個,如果不是同一個,如何做到事務管理的


(5) 導入實體類和映射文件
在這裏插入圖片描述
修改applicationContext.xml 引入hbm映射

<!-- 第三部分: 引入hbm -->
<property name="mappingResources">
	<list>
		<value>cn/itcast/storemanager/domain/Goods.hbm.xml</value>
		<value>cn/itcast/storemanager/domain/History.hbm.xml</value>
		<value>cn/itcast/storemanager/domain/Store.hbm.xml</value>
		<value>cn/itcast/storemanager/domain/Userinfo.hbm.xml</value>
	</list>
</property>

【擴展知識】批量映射配置:

<!-- 可以批量引入hbm
			支持通配符
		 -->
<property name="mappingLocations">
	<list>
		<value>classpath:cn/itcast/storemanager/domain/*.hbm.xml</value>
	</list>
</property>

3.登錄功能的實現(三層架構搭建、通用代碼抽取、基於泛型的通用Dao)

【業務邏輯梳理】
用戶在頁面輸入用戶和密碼 -----> 數據封裝model —>- 通過Service傳遞到Dao 查詢數據庫 —> 返回Userinfo對象 ---->
如果正確,將登錄信息保存到session,
如果不正確,封裝ActionError 返回登錄頁面。


3.1 修改登錄頁面表單

使用struts2 標籤,因爲需要回顯功能,新增struts標籤的taglib:

<%@ taglib uri="/struts-tags" prefix="s" %>

修改原來的form:

<s:form name="loginForm" action="user_login" namespace="/" method="post" cssStyle="margin-top:250px;">
	......
</s:form>

注意:struts2標籤使用的標準的html標籤,因此,屬性必須加雙引號。

修改其他標籤:

用戶名:<s:textfield name="name" cssClass="tx" maxLength="15" size="15" />
密碼:<s:password name="password" cssClass="tx" maxLength="15" size="15"/>

增加驗證信息顯示標籤:

<TR>
	<TD align="center" colSpan=3 width="623" height="260"
		background="<c:url value='/picture/welcome_01.gif'/>">
		<!-- 驗證碼返回提示 --> <br> <br> <br> <br> <br>
		<font color="#ff60a0" size="5">
			<!-- 回顯錯誤表單信息:表單校驗錯誤,針對具體字段 -->
			<s:fielderror></s:fielderror>
			<!-- 回顯一般錯誤信息 -->
			<s:actionerror/>
		</font>
	</TD>
</TR>

3.2 編寫UserAction (表現層 )

簡化抽取Action類,讓Action類繼承BaseAction:
優勢:

  • 使得所有Action都繼承ActionSupport。
  • 實現模型驅動的接口全部放置到BaseAction類中完成。
  • 將Action傳遞值的操作封裝到BaseAction中完成

第一步:在cn.itcast.storemanager.web.action中創建BaseAction的類:代碼:

//action的父類:用來存放action的重複代碼的
//通用:泛型
public abstract class BaseAction<T>  extends ActionSupport implements ModelDriven<T>{
	//實例化數據模型T,模型驅動必須實例化
//	private T t = new T();
	//子類可見
	protected T model;//沒有初始化

	public T getModel() {
		return model;
	}
	
	//在默認的構造器初始化數據模型
	public BaseAction() {
		//在子類初始化的時候,默認會調用父類的構造器
		//反射機制:獲取具體的類型
		//得到帶有泛型的類型,如BaseAction<Userinfo>
		Type superclass = this.getClass().getGenericSuperclass();
		//轉換爲參數化類型
		ParameterizedType parameterizedType = (ParameterizedType) superclass;
		//獲取泛型的第一個參數的類型類,如Userinfo
		Class<T> modelClass = (Class<T>) parameterizedType.getActualTypeArguments()[0];
		
		//實例化數據模型類型
		try {
			model = modelClass.newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}

	}
	
	//封裝值棧的操作的方法
	//root棧:棧頂map,可以通過key獲取value
	protected void setToValueStackRoot(String key,Object value){
		ActionContext.getContext().getValueStack().set(key, value);
	}
	
	//root棧:棧頂對象(匿名)
	protected void pushToValueStackRoot(Object value){
		ActionContext.getContext().getValueStack().push(value);
	}
	//map棧:--推薦,可以通過key獲取value
	protected void putToValueStackMap(String key,Object value){
		ActionContext.getContext().put(key, value);
	}
	
	//root棧:action的屬性
	protected Object result;
	public Object getResult() {//action在root棧,因此,result也在root棧
		return result;
	}
}

繼承ActionSupport
繼承ActionSupport是創建action的三種方法之一

實現ModelDriven<T>
實現ModelDriven<T>請求參數的接收機制中的模型驅動

第二步:創建UserAction子類代碼:用來接收頁面傳遞的user_login。

//用戶操作的action
//public class UserAction extends ActionSupport implements ModelDriven<Userinfo>{
public class UserAction extends BaseAction<Userinfo>{
//	//數據模型對象,在BaseAction中體現
//	private Userinfo userinfo = new Userinfo();
//	@Override
//	public Userinfo getModel() {
//		return userinfo;
//	}
	
	//注入service
	private IUserService userService;

	public void setUserService(IUserService userService) {
		this.userService = userService;
	}

	//業務方法:登錄
	public String login(){
		System.out.println("用戶開始登錄了。。。。"+model);
		//調用業務層,查詢
		Userinfo loginUser= userService.login(model);
		if(loginUser == null){
			//登錄失敗
			//將失敗信息打印頁面
			addActionError("您的用戶名或密碼錯誤請重試");
			//跳轉到登錄
			return "loginjsp";
			
		}else{
			//登錄成功
			//將登錄用戶放入session
//			ServletActionContext.getRequest().getSession().setAttribute("loginUser", loginUser);
			ServletUtils.setLoginUserToSession(loginUser);
			//代碼:可以抽取到baseAction,也可以抽取到工具類中
			//跳轉到主頁
			return SUCCESS;
		}
	}
	
}

簡單分析:
當子類實例化的時候,會將具體的類型來代替參數化父類的T,並自動執行父類的構造方法,通過反射機制,得到具體的模型的實例。struts通過getModel得到模型對象。
開發小技巧:
1.可以先將代碼寫完再統一配置(因爲配置的時候需要代碼)
2.代碼可以從前往後寫(從頁面–>Action–>Service–>Dao),也可以從後往前寫(先寫Dao,再寫Action和Service)。

第三步:在cn.itcast.storemanager.utils包中抽取工具類ServletUtils.java:

//操作servlet相關
public class ServletUtils {
	
	//登錄用戶的key
	private static final String LOGIN_USER="loginUser";//常量
	
	//將登陸了用戶放入session
	//滿足登錄和退出
	public static void setLoginUserToSession(Userinfo loginUser){
//		ServletActionContext.getRequest().getSession().setAttribute("loginUser", loginUser);
		if(null==loginUser){
			//清除登錄用戶
			ServletActionContext.getRequest().getSession().removeAttribute(LOGIN_USER);
		}else{
			//放登錄用戶
			ServletActionContext.getRequest().getSession().setAttribute(LOGIN_USER, loginUser);
		}
	}
	//從session獲取登錄用戶信息
	public static Userinfo getLoginUserFromSession(){
//		return (Userinfo)ServletActionContext.getRequest().getSession().getAttribute("loginUser");
		Object o = ServletActionContext.getRequest().getSession().getAttribute(LOGIN_USER);
		return o == null?null:(Userinfo)o;
	}

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章