09ssm_Spring的数据库编程

学习内容

一、Spring JDBC

  • 数据库的操作由持久层来实现
  • 传统JDBC开发中,会频繁的打开、关闭数据库连接等资源操作,产生大量重复代码,效率低
  • Spring JDBC是Spring框架提供的持久层功能,可以管理数据库连接资源,简化传统JDBC操作,提升数据库操作效率

1.JdbcTemplate概述

  • Spring框架提供了JdbcTemplate类操作数据库,包含的基本方法有数据的添加、删除、查询和更新
  • JdbcTemplate类继承自抽象类JdbcAccessor,实现了JdbcTemplate接口
  • JdbcAccessor抽象类中提供访问数据库时的公共属性如下:
    • DataSource: 获取设置数据库连接,还提供对数据库连接的缓冲池和分布式事务的支持
    • SQLExceptionTranslator:是一个接口,它负责对SQLException异常进行转译工作

2.Spring JDBC的配置

  • Spring JDBC开发依赖的jar包:spring-jdbc.jar、spring-tx.jar、mysql-connector-java.jar
  • spring-jdbc.jar包结构如下

  • spring-jdbc主要由4个包组成,具体如下
包名 说明
core(核心包) 包含了JDBC的核心功能。****包括JdbcTemplate类、SimpleJdbcInsert类、SimpleJdbcCall类以及NamedParameterJdbcTemplate类。
dataSource(数据源包) 包含****访问数据源的实用工具类。有多种数据源的实现,可以在Java EE容器外部测试JDBC代码。
object(对象包) 以面向对象的方式访问数据库,它可以执行查询、修改和更新操作并将返回结果作为业务对象,并且在数据表的列和业务对象的属性之间映射查询结果
support(支持包) 包含core和object包的支持类,如提供异常转换功能的SQLException类。
  • 使用JDBC的步骤
    • 第一步,导入相关依赖包spring-jdbc.jar、spring-tx.jar、mysql-connector-java.jar
    • 第二步,在spring的配置文件中进行如下配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1.配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库驱动-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <!--用户名-->
        <property name="username" value="root"/>
        <!--密码-->
        <property name="password" value="123456"/>
        <!--连接数据库url-->
        <property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
    </bean>

    <!--2.配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!--3.配置注入类-->
    <bean id="xxx" class="Xxx">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>
- dataSource数据源配置说明:使用DriverManagerDataSource类,4个属性含义如下。
属性名 含义
driverClassName 所使用的驱动名称,对应驱动JAR包中的Driver类
url 数据源地址
username 访问数据库的用户名
password 访问数据库的密码

二、JdbcTemplate的常用方法

实验环境准备

第一步:在ssm_spring项目下建spring-chap09-jdbc模块,目录结构如下:

第二步:为项目的模块添加依赖,依赖06单元配置好的lib库,还要加上jdbc的依赖包相关依赖包spring-jdbc.jar、spring-tx.jar、mysql-connector-java.jar(必须包含下图中的jar包)

1.execute()方法

  • execute()方法用业执行sql语句
  • 语法格式如下
    • jdbcTemplate.execute("sql语句")

【示例】spring框架下,使用JdbcTemplate类在数据spring中创建account数据表。创表SQL语句如下:

create table account(
    id int primary key auto_increment,
    username varchar(50),
    balance double
);

实现步骤如下:

(1)创建数据库spring

(2)确保按实验环境导入必要的依赖包

(3)编写spring配置文件spring-config.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1.配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库驱动-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <!--用户名-->
        <property name="username" value="root"/>
        <!--密码-->
        <property name="password" value="123456"/>
        <!--连接数据库url-->
        <property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
    </bean>

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

(4)编写execute()方法使用测试类

  • 为了方便测试,请导入单元测试包junit.jar
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

public class TestJDBC {
    @Test
    public void testExecute() {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        JdbcTemplate jdbc = context.getBean(JdbcTemplate.class);
        String sql = "create table account(" +
                     "id int primary key auto_increment," +
                     "username varchar(50)," +
                     "balance double" +
                     ")";
        jdbc.execute(sql);
        System.out.println("account数据表创建成功!");
    }
}

查看结果:在数据库spring中有了account数据表。

2.update()方法

  • update()方法用来执行插入、删除、修改数据表的SQL语句
  • 常用update()方法如下
****方法 ****说明
****int update(String sql) 该方法是最简单的update()方法重载形式,它直接执行传入的SQL语句,并返回受影响的行数。
****int update(String sql,Object… args) 该方法可以为SQL语句设置多个参数,这些参数保存在参数args中,使用Object…设置SQL语句中的参数,要求参数不能为NULL,并返回受影响的行数。
int update(PreparedStatementCreator psc) 该方法执行参数psc返回的语句,然后返回受影响的行数。
int update(String sql, PreparedStatementSetter pss) 该方法通过参数pss设置SQL语句中的参数,并返回受影响的行数。

【示例】完成对上述示例中account数据表的插入、删除、修改操作。

第一步:确认spring数据库中存在数据表account

第二步:确认Spring JDBC开发的依赖包均已导入

第三步:编写account数据表的pojo类**(放com.jdbc.pojo包)**

package com.jdbc.pojo;

public class Account {
    private int id;
    private String username;
    private double balance;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", balance='" + balance + '\'' +
                '}';
    }
}

第四步,编写数据持久层 -- dao层**(放com.jdbc.dao包)**

  • 编写dao层接口
package com.jdbc.dao;

import com.jdbc.pojo.Account;

public interface AccountDao {
    //添加记录
    int addAccount(Account account);
    //按id修改记录
    int updateAccountById(Account account);
    //按id修改存款金额
    int updateBalanceById(int id,double balance);
    //按id删除记录
    int deleteAccountById(int id);
}
  • 编写类,实现dao层接口
package com.jdbc.dao;

import com.jdbc.pojo.Account;
import org.springframework.jdbc.core.JdbcTemplate;

public class AccountDaoImpl implements AccountDao {
    //定义JdbcTemplate对象
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
  //添加记录
    @Override
    public int addAccount(Account account) {
        String sql = "insert account (username,balance) values(?,?)";
         //设置参数对象
        Object args[]=new Object[]{ account.getUsername(), account.getBalance() };
        int row = jdbcTemplate.update(sql, args);
        return row;
    }
  //按id修改记录
    @Override
    public int updateAccountById(Account account) {
        String sql = "update account set username=?,balance=? where id=?";
        //设置参数对象
        Object args[]=new Object[]{ account.getUsername(), account.getBalance(), account.getId() };
        int row = jdbcTemplate.update(sql, args);
        return row;
    }
    //按id修改存款金额
    @Override
    public int updateBalanceById(int id, double balance) {
        String sql = "update account set balance=? where id=?";
        Object args[]=new Object[]{balance,id};
        int row = jdbcTemplate.update(sql, args);
        return row;
    }
  //删除记录
    @Override
    public int deleteAccountById(int id) {
        String sql = "delete from account where id=?";
        int row = jdbcTemplate.update(sql, id);
        return row;
    }
}

第五步,设置spring配置文件spring-config.xml**(放resources文件夹)**

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1.配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库驱动-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <!--用户名-->
        <property name="username" value="root"/>
        <!--密码-->
        <property name="password" value="123456"/>
        <!--连接数据库url-->
        <property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
    </bean>

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

    <!--3.配置注入类-->
    <bean id="accountDao" class="com.jdbc.dao.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>

第六步,编写测试类

import com.jdbc.dao.AccountDao;
import com.jdbc.pojo.Account;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

public class TestJDBC {
    @Test
    public void testUpdate_add() {
        //初始化spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //从spring容器中获取AccountDao实例
        AccountDao dao = context.getBean(AccountDao.class);
        //设置要添加的数据
        Account account=new Account();
        account.setUsername("Tom");
        account.setBalance(1000.00);
        //调用dao的方法添加记录
        int row = dao.addAccount(account);
        if(row>0){
            System.out.println(row+"条记录插入成功!");
        }else {
            System.out.println("插入记录失败!");
        }
    }
    @Test
    public void testUpdate_update() {
        //初始化spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //从spring容器中获取AccountDao实例
        AccountDao dao = context.getBean(AccountDao.class);
        //设置要修改的数据
        Account account=new Account();
        account.setId(1);
        account.setUsername("Tom");
        account.setBalance(1500.00);
        //调用dao的updateAccountById方法修改记录
        int row = dao.updateAccountById(account);
        if(row>0){
            System.out.println(row+"条记录修改成功!");
        }else {
            System.out.println("修改记录失败!");
        }
        
        System.out.println("==============================");
    
        //调用dao的updateBalanceById方法修改记录
        row = dao.updateBalanceById(2,2680.00);
        if(row>0){
            System.out.println(row+"条记录修改成功!");
        }else {
            System.out.println("修改记录失败!");
        }
    }
    @Test
    public void testUpdate_delete() {
        //初始化spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //从spring容器中获取AccountDao实例
        AccountDao dao = context.getBean(AccountDao.class);
        //调用dao的方法删除记录
        int row = dao.deleteAccountById(2);
        if(row>0){
            System.out.println(row+"条记录删除成功!");
        }else {
            System.out.println("删除记录失败!");
        }
    }
}

3.query()方法

  • query()方法用来执行查询数据表的SQL语句
  • 常用query()方法如下
****方法 ****说明
****List query(String sql, RowMapper rowMapper) 执行String类型参数提供的SQL语句,并****通过参数rowMapper返回一个List类型的结果
****List query(String sql,Object[] args,RowMapper rowMapper) 使用****Object[]的值来设置SQL语句中的参数值,rowMapper是个回调方法,直接返回List类型的数据。
****queryForObject(String sql,RowMapper rowMapper, Object… args) 将args参数绑定到SQL语句中,并通过****参数rowMapper返回一个Object类型的单行记录
queryForObject(String sql,RowMapper rowMapper, Object… args) List query(String sql, PreparedStatementSetter pss, RowMapper rowMapper) 根据String类型参数提供的SQL语句创建PreparedStatement对象,通过参数rowMapper将结果返回到List中。
queryForList(String sql,Object[] args, class elementType) 该方法可以返回多行数据的结果,但必须返回列表,args参数是sql语句中的参数,elementType参数返回的是List数据类型。

【示例】完成对上述示例中account数据表的查询操作。

第一步:确认spring数据库中存在数据表account。如果没有,可以运行如下脚本:

USE spring;
DROP TABLE IF EXISTS account;
# 创建数据表
CREATE TABLE account (
    id int(11) NOT NULL AUTO_INCREMENT,
    username varchar(50) DEFAULT NULL,
    balance double DEFAULT NULL,
    PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
# 插入数据
insert into account(id,username,balance) values
(1,'zhangsan',100),(2,'lisi',500),(3,'wangwu',300);

第二步:确认Spring JDBC开发的依赖包均已导入

第三步:编写account数据表的pojo类**(同前)**

第四步,编写数据持久层 -- dao层**(放com.jdbc.pojo包)**

  • 编写dao层接口
package com.jdbc.dao;

import com.jdbc.pojo.Account;

import java.util.List;

public interface AccountDao {
  //按id查询数据表account
    Account findAccountById(int id);
    //查询数据表account所有记录
    List<Account> findAllAccount();
}
  • 编写类,实现dao层接口
package com.jdbc.dao;

import com.jdbc.pojo.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.util.List;

public class AccountDaoImpl implements AccountDao {
    //定义JdbcTemplate对象
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
  //按id查询数据表account
    @Override
    public Account findAccountById(int id) {
        String sql = "select * from account where id=?";
        RowMapper<Account> rowMapper = new BeanPropertyRowMapper<>(Account.class);
        Account account = jdbcTemplate.queryForObject(sql, rowMapper, id);
        return account;
    }
  //查询数据表account所有记录
    @Override
    public List<Account> findAllAccount() {
        String sql = "select * from account";
        RowMapper<Account> rowMapper = new BeanPropertyRowMapper<>(Account.class);
        List<Account> accounts = jdbcTemplate.query(sql, rowMapper);
        return accounts;
    }
}

第五步,设置spring配置文件spring-config.xml**(同前)**

第六步,编写测试类

import com.jdbc.dao.AccountDao;
import com.jdbc.pojo.Account;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class TestJDBC {
    @Test
    public void testQuery() {
        //初始化spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //从spring容器中获取AccountDao实例
        AccountDao dao = context.getBean(AccountDao.class);
        
        //调用dao的方法查询id号为3的记录并显示
        Account account = dao.findAccountById(3);
        System.out.println(account);

        System.out.println("======================");
        
    //调用dao的方法查询所有记录并显示
        List<Account> accounts = dao.findAllAccount();
        for (Account acc : accounts) {
            System.out.println(acc);
        }
    }
}

三、Spring事务管理概述

  • 实际开发中,数据库操作会涉及到事务管理。如:某商品入库时,明细表和库存表都要更新数据,为确保两表同时更新,需要事务管理
  • Spring提供了事务管理API,简化事务管理流程

1.事务管理核心接口

  • 事务管理依赖包:spring-tx.jar
  • 该包提供3个接口实现事务管理
    • PlatformTransactionManager接口:根据属性管理事务。
    • TransactionDefinition接口:定义事务的属性。
    • TransactionStatus接口:界定事务的状态 。

(1)PlatformTransactionManager接口

主要方法如下。

****方法 ****说明
TransactionStatus 获取事务状态信息
void commit(TransactionStatus status) 提交事务
void rollback(TransactionStatus status) 回滚事务

注意:

  • 实际应用中,Spring事务管理由具体的持久化技术完成,PlatformTransactionManager接口只提供统一的抽象方法。
  • 对于不同持久化技术,Spring提供了不同的实现类。如:为Spring JDBC和Mybatis等依赖DataSource的持久化技术提供了DataSourceTransactionManager实现类

(2)TransactionDefinition接口

  • TransactionDefinition接口中定义了事务描述相关的常量,事务描述相关常量包括:事务的隔离级别、传播行为、超时时间、是否为只读事务
    • 事务隔离级别: 有以下5几种。
****隔离级别 ****说明
ISOLATION_DEFAULT 采用当前数据库默认的事务隔离级别。
ISOLATION_READ_UNCOMMITTED 读未提交。允许另外一个事务读取到当前未提交的数据,隔离级别最低,****可能会导致脏读、幻读或不可重复读。 <br>脏读:********事务A在处理过程中读取了事务B未提交的数据。<br>****幻读: 事务A读取与搜索条件相匹配的若干行。之后,事务B以插入或删除行等方式来修改事务A的结果集,然后再提交。****可能造成事物A第一次与二次读的数据不同。<br>不可重复读:********一个事务范围内,****多次查询某个数据,由于数据没锁住,可能被别的事务更新导致可能得到不同的结果
ISOLATION_READ_COMMITTED 读已提交。被一个事务修改的数据提交后才能被另一个事务读取,****可以避免脏读,无法避免幻读,而且不可重复读
ISOLATION_ REPEATABLE_READ 允许重复读,可以避免脏读,资源消耗上升。这是MySQL数据库的默认隔离级别。
REPEATABLE_SERIALIZABLE 事务串行执行,也就是按照时间顺序一一执行多个事务,不存在并发问题,最可靠,但性能与效率最低。
- **事务传播行为:** 指处于不同事务中的方法在相互调用时,方法执行期间,事务的维护情况。有以下7种,开发人员要根据实际情况进行选择。
****传播行为 ****说明
PROPAGATION_REQUIRED 如果当前存在一个事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 ****默认的事务传播行为!!
PROPAGATION_SUPPORTS 支持当前事务。允许如果当前存在一个事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
PROPAGATION_MANDATORY 当前必须存在一个事务,如果没有,就抛出异常。
PROPAGATION_REQUIRES_NEW 创建一个新的事务。如果当前已存在一个事务,将已存在的事务挂起。
PROPAGATION_NOT_SUPPORTED 不支持事务。在没有事务的情况下执行,如果当前已存在一个事务,则将已存在的事务挂起。
ROPAGATION_NEVER 永远不支持当前事务。如果当前已存在一个事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在当前事务的一个子事务中执行。
- **事务超时时间:** 指事务执行的时间界限,超过这个时间界限,事务将会回滚
    - TransactionDefinition接口提供了**TIMEOUT_DEFAULT常量**定义事务的超时时间。
- **事务是否只读**
    - 当事务为只读时,该事务不修改任何数据。如果在只读事务中修改数据,会引发异常
    - 只读事务有助于提升性能
  • TransactionDefinition接口中获取事务属性的方法
****方法 ****说明
int getPropagationBehavior() 返回事务的传播行为
int getIsolationLevel() 返回事务的隔离层次
int getTimeout() 返回事务的超时属性
boolean isReadOnly() 判断事务是否为只读
String getName() 返回定义的事务名称

(3)TransactionStatus接口

  • 该接口用于界定事务状态
  • 提供返回事务状态信息的方法
****方法 ****说明
boolean isNewTransaction() 判断当前事务是否为新事务
boolean hasSavepoint() 判断当前事务是否创建了一个保存点
boolean isRollbackOnly() 判断当前事务是否被标记为rollback-only
void setRollbackOnly() 将当前事务标记为rollback-only
boolean isCompleted() 判断当前事务是否已经完成(提交或回滚)
void flush() 刷新底层的修改到数据库

2.事务管理的方式

有两种。

  • 编程式事务管理: 通过编写代码实现的事务管理。包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚。
  • 声明式事务管理:通过AOP技术实现的事务管理,其主要思想是将事务管理作为一个“切面”代码单独编写,然后通过AOP技术将事务管理的“切面”代码植入到业务目标类中。

四、声明式事务管理

1.基于XML方式的声明式事务

  • 在spring配置文件中,配置事务规则的相关声明来实现事务管理
  • XML文件配置声明式事务一般步骤
    • 第一步:导入依赖包spring-tx.jar、spring-aop.jar、aopalliance.jar、aspectjweaver.jar**(后三个提供AOP)**
    • 第二步:引入tx、aop约束(命名空间)
    • 第三步:配置事务管理器类DataSourceTransactionManager的bean,要注入DataSource属性
    • 第四步:用 tx:advice及其子元素 tx:attributes、 tx:method配置事务管理的通知。
      • tx:advice元素的transaction-manager属性指向第三步的bean
      • tx:method元素设置事务方法名、隔离级别、传播行为等。
    • 第五步:用aop:config及其子元素aop:pointcut、aop:advisor配置AOP,将通知切入到需要事务的方法中去
  • tx:advice元素属性说明如下:
  • id属性:配置文件中唯一标识
  • transaction-manager属性:指定事务管理器
  • tx:advice元素的子元素 tx:attributes下可以配置多个 tx:method子元素
  • tx:method子元素常用属性如下
****属性 ****说明
name 用于指定方法名的匹配模式,该属性为必选属性,它指定了与事务属性相关的方法名。
propagation 用于指定事务的传播行为。
isolation 用于指定事务的隔离级别。
read-only 用于指定事务是否只读。
timeout 用于指定事务超时时间。
rollback-for 用于指定触发事务回滚的异常类。
no-rollback-for 用于指定不触发事务回滚的异常类。

【示例】在前述有关account数据表操作示例的基础上,编写一个模拟银行转账的程序 ,要求转账时通过Spring对事务进行控制。

(1)导入依赖包

  • 事务管理包:spring-tx.jar
  • AOP相关包:spring-aop.jar、aopalliance.jar、aspectjweaver.jar
  • Spring JDBC数据库编程包:spring-jdbc.jar、mysql-connector-java-5.1.47.jar
  • Spring中bean管理相关包:spring-beans-5.3.12.jar、spring-context-5.3.12.jar、spring-core-5.3.12.jar、spring-expression-5.3.12.jar、commons-logging-1.2.jar
  • 如果想单元测试,还要导入junit.jar包

(2)编写dao层**(同前述有关account数据表的添加、删除、修改、查询操作示例)**

(3)编写service层

  • 编写AccountService接口**(放com.jdbc.service包)**
package com.jdbc.service;

public interface AccountService {
    /**
     * 转账
     * @param sourceId 转出账户的id
     * @param targetId 转入账户的id
     * @param money 转账金额
     */
    void transfer(int sourceId,int targetId,double money);
}
  • 编写类,实现AccountService接口**(放com.jdbc.service包)**
package com.jdbc.service;

import com.jdbc.dao.AccountDao;
import com.jdbc.pojo.Account;

public class AccountServiceImpl implements AccountService{
    private AccountDao accountDao;

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

    /**
     * 转账
     * @param sourceId 转出账户的id
     * @param targetId 转入账户的id
     * @param money 转账金额
     */
    @Override
    public void transfer(int sourceId, int targetId, double money) {
        //分别得到sourceId\targetId账户原金额
        double sourceMoney = accountDao.findAccountById(sourceId).getBalance();
        double targetMoney = accountDao.findAccountById(targetId).getBalance();

        //扣减sourceId账户金额数量
        accountDao.updateBalanceById(sourceId,sourceMoney-money);

        int x=1/0; //此处人为设置异常!!以便体会事务管理的重要性!

        //增加targetId账户金额数量
        accountDao.updateBalanceById(targetId,targetMoney+money);
    }
}

(4)设置spring配置文件spring-config.xml**(放resources文件夹)**

<?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: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/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--1.配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库驱动-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <!--用户名-->
        <property name="username" value="root"/>
        <!--密码-->
        <property name="password" value="123456"/>
        <!--连接数据库url-->
        <property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
    </bean>

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

    <!--3.配置注入类-->
    <!--使用属性注入-->
    <bean id="accountDao" class="com.jdbc.dao.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    <!--使用了自动装配-->
    <bean id="accountService" class="com.jdbc.service.AccountServiceImpl" autowire="byType"/>
</beans>

(5)编写测试类

import com.jdbc.dao.AccountDao;
import com.jdbc.pojo.Account;
import com.jdbc.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class TestJDBC {
    @Test
    public void testTransfer(){
        //初始化spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //从spring容器中获取AccountService实例
        AccountService service = context.getBean(AccountService.class);
        //调用转账方法
        service.transfer(2,3,200);
        System.out.println("转账成功...");
    }
}

注意:

  • 运行该测试类时,由于第4步没有配置事务管理,查看account数据表发现:转出账户存款减少,但转入账户存款并没有增加,数据处理有误。系统无法保证数据的安全性与一致性!!

(6)为了让操作数据库时,数据处理正确,应该在spring-config.xml加上如下事务管理配置

<!--4.配置事务-->
<!--(1)配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" autowire="byType"/>

<!--(2)编写通知:对事务进行增强,需要编写切入点和具体执行事务的细节-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- name属性值为*,表示所有方法-->
        <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
    </tx:attributes>
</tx:advice>

<!--(3)编写AOP:让Spring自动为目标生成代理,需要使用AspectJ的表达式-->
<aop:config>
    <!-- 切入点 -->
    <aop:pointcut id="txPointCut" expression="execution(* com.jdbc.service.*.*(..))"/>
    <!-- 切面:将切入点和通知整合 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

再次运行测试类,转入转出账户数据会同步处理时,同于切入了事务,保证了数据的安全性与一致性!!

2.基于注解方式的声明式事务

  • @Transactional注解:写在方法上方为该方法提供事务管理。有一系列属性配置事务,含义如下:
****属性 ****说明
value 用于指定使用的事务管理器
propagation 用于指定事务的传播行为
isolation 用于指定事务的隔离级别
timeout 用于指定事务的超时时间
readonly 用于指定事务是否为只读
rollbackFor 用于指定导致事务回滚的异常类数组
rollbackForClassName 用于指定导致事务回滚的异常类名称数组
noRollbackFor 用于指定不会导致事务回滚的异常类数组
noRollbackForClassName 用于指定不会导致事务回滚的异常类名称数组
  • 基于注解方式的声明式事务一般步骤
    • 第一步:导入依赖包spring-tx.jar、spring-aop.jar**(后一个提供AOP)**
    • 第二步:引入tx约束**(命名空间)**
    • 第三步:配置事务管理器类DataSourceTransactionManager的bean,要注入DataSource属性
    • 第四步:注册事务管理器驱动
<!--注册事务管理器-->
<tx:annotation-driven transaction-manager="事务管理器bean的id"/>
- 第五步:在需要事务管理的方法上方加注解@Transactional

【示例】使用注解完成前述模拟银行转账示例。

仅修改以下两个地方,其它不变!

(1)为AccountServiceImpl类的transfer方法上方加@Transactional注解

@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false)
public void transfer(int sourceId, int targetId, double money) {
    //分别得到sourceId\targetId账户原金额
    double sourceMoney = accountDao.findAccountById(sourceId).getBalance();
    double targetMoney = accountDao.findAccountById(targetId).getBalance();

    //扣减sourceId账户金额数量
    accountDao.updateBalanceById(sourceId,sourceMoney-money);

    int x=1/0; //此处人为设置异常

    //增加targetId账户金额数量
    accountDao.updateBalanceById(targetId,targetMoney+money);
}

(2)修改配置文件spring-config.xml,修改事务管理配置方法

<!--4.配置事务-->
<!--(1)配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" autowire="byType"/>
<!--(2)注册事务管理器-->
<tx:annotation-driven transaction-manager="transactionManager"/>

再一次运行测试类,查看数据结果,转入转出账户存款金额是否具有一致性!

五、课后练习

完成下述任务。

1.用注解方式完成对数据表account的插入、删除、修改、查询操作。

2.要求学生在控制台输入用户名密码,如果用户账号密码正确则显示用户所属班级,如果登录失败则显示登录失败。实现用户登录项目运行成功后控制台效果如下所示。

  • 数据库为spring
  • student数据表结构如下表所示:
字段名 类型 长度 是否主键 说明
id int 11 学生编号
username varchar 255 学生姓名
password varchar 255 学生密码
course varchar 255 学生班级
  • student数据表创建及数据添中脚本如下
use spring;
DROP TABLE IF EXISTS student;
#创建数据表
CREATE TABLE student (
  id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(255) DEFAULT NULL,
  password varchar(255) DEFAULT NULL,
  course varchar(255) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
#插入数据
INSERT INTO student VALUES ('1', 'zhangsan', '123456', 'Java');
INSERT INTO student VALUES ('2', 'lisi', '123456', 'HTML');
INSERT INTO student VALUES ('3', 'wangfang', '123456', 'c');
  • 思路分析:根据学生管理系统及其登录要求,案例的实现步骤如下:
    • 为了存储学生信息,需要创建一个数据库。
    • 为了程序连接数据库并完成对数据的增删改查操作,需要在XML配置文件中配置数据库连接和事务等信息。、
    • 在Dao层实现查询用户信息的方法。
    • 在Controller层处理业务逻辑,如判断用户输入的用户名与密码是否正确 。
  • 要求分别用基于注解方式和基于XML方式完成该任务

猜你喜欢

转载自blog.csdn.net/qq_43596041/article/details/129821147