Spring - Spring對JDBC和Hibernate的支持

JDBC

爲了使 JDBC 更加易於使用,Spring 在 JDBCAPI 上定義了一個抽象層, 以此建立一個JDBC存取框架.

作爲 SpringJDBC 框架的核心, JDBC 模板的設計目的是爲不同類型的JDBC操作提供模板方法. 每個模板方法都能控制整個過程,並允許覆蓋過程中的特定任務.通過這種方式,可以在儘可能保留靈活性的情況下,將數據庫存取的工作量降到最低.

引入Spring框架相關的jar包以及c3p0和mysql連接jar包。

  • c3p0-0.9.1.2.jar
  • com.springsource.net.sf.cglib-2.2.0.jar
  • com.springsource.org.aopalliance-1.0.0.jar
  • com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
  • commons-logging-1.1.3.jar
  • mysql-connector-java-5.1.7-bin.jar
  • spring-aop-4.0.0.RELEASE.jar
  • spring-aspects-4.0.0.RELEASE.jar
  • spring-beans-4.0.0.RELEASE.jar
  • spring-context-4.0.0.RELEASE.jar
  • spring-core-4.0.0.RELEASE.jar
  • spring-expression-4.0.0.RELEASE.jar
  • spring-jdbc-4.0.0.RELEASE.jar
  • spring-orm-4.0.0.RELEASE.jar
  • spring-tx-4.0.0.RELEASE.jar
  • spring-web-4.0.0.RELEASE.jar
  • spring-webmvc-4.0.0.RELEASE.jar

User.java

public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer balance;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public Integer getBalance() {
        return balance;
    }
    public void setBalance(Integer balance) {
        this.balance = balance;
    }
    public User(Integer id, String username, String password, Integer balance) {
        super();
        this.id = id;
        this.username = username;
        this.password = password;
        this.balance = balance;
    }
    public User() {
        super();
    }
    @Override
    public String toString() {
        return "Users [id=" + id + ", username=" + username + ", password="
                + password + ", balance=" + balance + "]";
    }
}

db.properties

jdbc.user=root
jdbc.password=1230
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql:///test

jdbc.initPoolSize=5
jdbc.maxPoolSize=10

applicationContext.xml

<!-- 導入資源文件 -->
<context:property-placeholder location="classpath:db.properties"/>

<!-- 配置C3P0數據源 -->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
    <property name="user" value="${jdbc.user}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="driverClass" value="${jdbc.driverClass}"></property>
    <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>

    <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
    <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>

<!-- 配置Spring的JdbcTemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

創建一個測試類對JDBCTemplate的方法進行測試
JDBCTest.java

public class JDBCTest {

    private ApplicationContext ctx = null;
    private JdbcTemplate jdbcTemplate = null;

    {
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
    }

    @Test
    public void testDataSource() throws SQLException {
        DataSource dataSource = ctx.getBean(DataSource.class);
        System.out.println(dataSource.getConnection());
    }

    /**
     * 執行INSERT,UPDATE,DELETE
     */
    @Test
    public void testUpdate(){
        String sql = "insert into users values(3, 'kety', '123', 200)";
        jdbcTemplate.update(sql);
    }


    /**
     * 執行批量更新:批量的INSERT,UPDATE,DELETE
     * 最後一個參數是Object[]的List類型:因爲修改一條記錄需要一個Object數組,那麼多條就需要多個Object的數組
     */
    @Test
    public void testBatchUpdate(){
        String sql = "insert into users(id,username,password,balance) values(?,?,?,?)";
        List<Object[]> batchArgs = new ArrayList<Object[]>();
        batchArgs.add(new Object[]{6, "AA", "123", 1000});
        batchArgs.add(new Object[]{7, "BB", "123", 2000});
        batchArgs.add(new Object[]{8, "CC", "123", 3000});
        batchArgs.add(new Object[]{9, "DD", "123", 4000});
        jdbcTemplate.batchUpdate(sql, batchArgs);
    }

    /**
     * 從數據庫中獲取一條記錄, 實際得到對應的一個對象
     * 注意不是調用 queryForObject(String sql, Class<Employee> requiredType, Object... args) 方法!
     * 而需要調用 queryForObject(String sql, RowMapper<Employee> rowMapper, Object... args)
     * 1. 其中的 RowMapper 指定如何去映射結果集的行, 常用的實現類爲 BeanPropertyRowMapper
     * 2. 使用 SQL 中列的別名完成列名和類的屬性名的映射. 例如 last_name lastName
     * 3. 不支持級聯屬性. JdbcTemplate 到底是一個 JDBC 的小工具, 而不是 ORM 框架
     */
    @Test
    public void testQueryForObject(){
        String sql = "select id, username, password, balance from users where id=?";
        RowMapper<User> rowMapper = new BeanPropertyRowMapper<User>(User.class);
        User user = jdbcTemplate.queryForObject(sql, rowMapper, 6);
        System.out.println("User:" + user);
    }

    /**
     * 查到實體類的集合
     * 注意調用的不是 queryForList 方法
     */
    @Test
    public void testQueryForList(){
        String sql = "select id, username, password, balance from users where id<?";
        RowMapper<User> rowMapper = new BeanPropertyRowMapper<User>(User.class);
        List<User> users = jdbcTemplate.query(sql, rowMapper, 10);
        System.out.println(users);
    }

    /**
     * 獲取單個列的值, 或做統計查詢
     * 使用 queryForObject(String sql, Class<Long> requiredType) 
     */
    @Test
    public void testQueryForObject2(){
        String sql = "select count(id) from users";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println("count:" + count);
    }
}

在實際的使用中,一般會創建一個dao類來封裝對某個對象的所有增刪改查操作.

UserDao.java

@Repository
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public User get(Integer id){
        String sql = "select id, username, password, balance from users where id=?";
        RowMapper<User> rowMapper = new BeanPropertyRowMapper<User>(User.class);
        User user = jdbcTemplate.queryForObject(sql, rowMapper, 6);
        return user;
    }
}

在applicationContext.xml文件中增加標籤

<context:component-scan base-package="com.atguigu.spring.jdbc"></context:component-scan>

JDBCTest.java

public class JDBCTest {

    private ApplicationContext ctx = null;
    private JdbcTemplate jdbcTemplate = null;
    private UserDao userDao = null;

    {
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
        userDao = ctx.getBean(UserDao.class);
    }

    @Test
    public void testUserDao(){
        System.out.println(userDao.get(1));
    }

}

在JdbcTemplate中使用具名參數

在經典的 JDBC 用法中, SQL 參數是用佔位符 ? 表示,並且受到位置的限制. 定位參數的問題在於, 一旦參數的順序發生變化, 就必須改變參數綁定.

在 Spring JDBC 框架中, 綁定 SQL 參數的另一種選擇是使用具名參數(named parameter).

具名參數: SQL 按名稱(以冒號開頭)而不是按位置進行指定. 具名參數更易於維護, 也提升了可讀性. 具名參數由框架類在運行時用佔位符取代

具名參數只在 NamedParameterJdbcTemplate 中得到支持

步驟:
在applicationContext.xml中添加代碼

<!-- 配置 NamedParameterJdbcTemplate, 該對象可以使用具名參數, 其沒有無參數的構造器, 所以必須爲其構造器指定參數 -->
<bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" id="namedParameterJdbcTemplate">
    <constructor-arg ref="dataSource"></constructor-arg>
</bean>

JDBCTest.java

public class JDBCTest {

    private ApplicationContext ctx = null;
    private JdbcTemplate jdbcTemplate = null;
    private UserDao userDao = null;
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    {
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
        userDao = ctx.getBean(UserDao.class);
        namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class);
    }

    /**
     * 可以爲參數起名字. 
     * 1. 好處: 若有多個參數, 則不用再去對應位置, 直接對應參數名, 便於維護
     * 2. 缺點: 較爲麻煩. 
     */
    @Test
    public void testNamedParameterJdbcTemplate(){
        String sql = "insert into users(id,username, password, balance) values(:id, :username, :password, :balance)";
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put("id", 10);
        paramMap.put("username", "Herry");
        paramMap.put("password", "1234");
        paramMap.put("balance", 1000);

        namedParameterJdbcTemplate.update(sql, paramMap);
    }

    /**
     * 使用具名參數時, 可以使用 update(String sql, SqlParameterSource paramSource) 方法進行更新操作
     * 1. SQL 語句中的參數名和類的屬性一致!
     * 2. 使用 SqlParameterSource 的 BeanPropertySqlParameterSource 實現類作爲參數. 
     */
    @Test
    public void testNamedParameterJdbcTemplate2(){
        String sql = "insert into users(id,username, password, balance) values(:id, :username, :password, :balance)";
        User user = new User();
        user.setId(11);
        user.setBalance(1000);
        user.setUsername("Beme");
        user.setPassword("123456");
        SqlParameterSource paramSource = new BeanPropertySqlParameterSource(user);

        namedParameterJdbcTemplate.update(sql, paramSource);
    }
}

事務

聲明式事務管理: 大多數情況下比編程式事務管理更好用. 它將事務管理代碼從業務方法中分離出來, 以聲明的方式來實現事務管理. 事務管理作爲一種橫切關注點, 可以通過 AOP 方法模塊化. Spring 通過 Spring AOP 框架支持聲明式事務管理.

聲明式事務

BookShopDao.java

public interface BookShopDao {

    //根據書號獲取書的單價
    public int findBookPriceByIsbn(String isbn);

    //更新數的庫存. 使書號對應的庫存 - 1
    public void updateBookStock(String isbn);

    //更新用戶的賬戶餘額: 使 username 的 balance - price
    public void updateUserAccount(String username, int price);
}

BookShopDaoImpl.java

@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int findBookPriceByIsbn(String isbn) {
        String sql = "select price from book where isbn=?";     
        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    @Override
    public void updateBookStock(String isbn) {
        // 檢查書的庫存是否足夠,如果不夠拋出異常
        String sql2 = "select stock from book_stock where isbn=?";
        int num = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if(num == 0){
            throw new BookStockException("庫存不足");
        }

        String sql = "update book_stock set stock=stock-1 where isbn=?";
        jdbcTemplate.update(sql, isbn);
    }

    @Override
    public void updateUserAccount(String username, int price) {
        //判斷賬戶餘額,如果不足則拋出異常
        String sql2 = "select balance from account where username=?";
        int num = jdbcTemplate.queryForObject(sql2, Integer.class, username);
        if(num < price){
            throw new UserAccountException("餘額不足");
        }
        String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
        jdbcTemplate.update(sql, price, username);
    }
}

BookShopService.java

public interface BookShopService {
    public void purchase(String username, String isbn);
}

BookShopServiceImpl.java

@Service
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;

    //添加事務註解
    @Transactional
    @Override
    public void purchase(String username, String isbn) {
        //1. 獲取圖書單價
        int price = bookShopDao.findBookPriceByIsbn("1001");

        //2. 減去圖書庫存
        bookShopDao.updateBookStock("1001");
        //3. 減去賬戶餘額
        bookShopDao.updateUserAccount(username, price);
    }
}

applicationContext.xml

<context:component-scan base-package="com.atguigu.spring.*"></context:component-scan>

<!-- 導入資源文件 -->
<context:property-placeholder location="classpath:db.properties"/>

<!-- 配置C3P0數據源 -->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
    <property name="user" value="${jdbc.user}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="driverClass" value="${jdbc.driverClass}"></property>
    <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>

    <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
    <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>

<!-- 配置Spring的Template -->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 配置事務管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 啓用事務註解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

SpringTranscationTest.java

public class SpringTranscationTest {

    private ApplicationContext ctx = null;
    private BookShopDao bookShopDao = null;
    private BookShopService bookShopService = null;

    {
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        bookShopDao = (BookShopDao) ctx.getBean("bookShopDao");
        bookShopService = ctx.getBean(BookShopService.class);
    }

    @Test
    public void testBookShopService(){
        bookShopService.purchase("AA", "1001");
    }
}

事務的傳播行爲

當事務方法被另一個事務方法調用時, 必須指定事務應該如何傳播. 例如: 方法可能繼續在現有事務中運行, 也可能開啓一個新事務, 並在自己的事務中運行.

事務的傳播行爲可以由傳播屬性指定. Spring 定義了 7 種類傳播行爲.
這裏寫圖片描述

REQUIRED 傳播行爲
當 bookService 的 purchase() 方法被另一個事務方法 checkout() 調用時, 它默認會在現有的事務內運行. 這個默認的傳播行爲就是 REQUIRED. 因此在 checkout() 方法的開始和終止邊界內只有一個事務. 這個事務只在 checkout() 方法結束的時候被提交, 結果用戶一本書都買不了

事務傳播屬性可以在 @Transactional 註解的 propagation 屬性中定義
這裏寫圖片描述

REQUIRED_NEW 傳播行爲
另一種常見的傳播行爲是 REQUIRES_NEW. 它表示該方法必須啓動一個新事務, 並在自己的事務內運行. 如果有事務在運行, 就應該先掛起它.

這裏寫圖片描述

根據剛纔聲明式事務的例子,增加結賬的功能
Cashier.java

public interface Cashier {
    public void checkout(String username, List<String> isbns);
}

CashierImpl.java

@Service("cashier")
public class CashierImpl implements Cashier{

    @Autowired
    private BookShopService bookShopService;

    @Transactional
    @Override
    public void checkout(String username, List<String> isbns) {
        for(String isbn : isbns){
            bookShopService.purchase(username, isbn);
        }
    }
}

SpringTranscationTest.java

public class SpringTranscationTest {

    private ApplicationContext ctx = null;
    private BookShopDao bookShopDao = null;
    private BookShopService bookShopService = null;
    private Cashier cashier = null;

    {
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        bookShopDao = (BookShopDao) ctx.getBean("bookShopDao");
        bookShopService = ctx.getBean(BookShopService.class);
        cashier = (Cashier) ctx.getBean("cashier");
    }

    @Test
    public void testTransactionPropagation(){
        cashier.checkout("AA", Arrays.asList("1001", "1002"));
    }
}

事務的其他屬性

隔離級別

使用 isolation 指定事務的隔離級別, 最常用的取值爲 READ_COMMITTED

回滾

默認情況下 Spring 的聲明式事務對所有的運行時異常進行回滾. 也可以通過對應的屬性進行設置. 通常情況下取默認值即可.

只讀

使用 readOnly 指定事務是否爲只讀. 表示這個事務只讀取數據但不更新數據, 這樣可以幫助數據庫引擎優化事務. 若真的事一個只讀取數據庫值的方法, 應設置 readOnly=true

過期

使用 timeout 指定強制回滾之前事務可以佔用的時間.

BookShopServiceImpl.java

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
    @Autowired
    private BookShopDao bookShopDao;

    //添加事務註解
//  @Transactional(propagation=Propagation.REQUIRES_NEW,
//          isolation=Isolation.READ_COMMITTED,
//          noRollbackFor={UserAccountException.class})
    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            readOnly=false,
            timeout=3)
    @Override
    public void purchase(String username, String isbn) {

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {}

        //1. 獲取書的單價
        int price = bookShopDao.findBookPriceByIsbn(isbn);

        //2. 更新數的庫存
        bookShopDao.updateBookStock(isbn);

        //3. 更新用戶餘額
        bookShopDao.updateUserAccount(username, price);
    }
}

使用XML文件的方式配置事務

applicationContext-tx-xml.xml

<!-- 配置 bean -->
<bean id="bookShopDao" class="com.atguigu.spring.tx.xml.BookShopDaoImpl">
    <property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>

<bean id="bookShopService" class="com.atguigu.spring.tx.xml.service.impl.BookShopServiceImpl">
    <property name="bookShopDao" ref="bookShopDao"></property>
</bean>

<bean id="cashier" class="com.atguigu.spring.tx.xml.service.impl.CashierImpl">
    <property name="bookShopService" ref="bookShopService"></property>
</bean>

<!-- 1. 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 2. 配置事務屬性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 根據方法名指定事務的屬性 -->
        <tx:method name="purchase" propagation="REQUIRES_NEW"/>
        <tx:method name="get*" read-only="true"/>
        <tx:method name="find*" read-only="true"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

<!-- 3. 配置事務切入點, 以及把事務切入點和事務屬性關聯起來 -->
<aop:config>
    <aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))" 
        id="txPointCut"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>  
</aop:config>

Hibername

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