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

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值