Spring框架学习(三)——JDBCTemplate与事务属性

四、JDBCTemplate

1. 概述

为了使JDBC更加易于使用,Spring在JDBC API上定义了一个抽象层,以此建立一个JDBC存取框架。
作为Spring JDBC框架的核心,JDBC模板的设计目的是为不同类型的JDBC操作提供模板方法,通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低。
可以将Spring的JdbcTemplate看作是一个小型的轻量级持久化层框架,和我们之前使用过的DBUtils风格非常接近。

2. 环境准备

导入JAR包

  1. IOC容器所需要的JAR包
    commons-logging-1.1.1.jar
    spring-beans-4.3.18.RELEASE.jar
    spring-context-4.3.18.RELEASE.jar
    spring-core-4.3.18.RELEASE.jar
    spring-expression-4.3.18.RELEASE.jar
  2. JdbcTemplate所需要的JAR包
    spring-jdbc-4.3.18.RELEASE.jar
    spring-orm-4.3.18.RELEASE.jar
    spring-tx-4.3.18.RELEASE.jar
  3. 数据库驱动和数据源
    c3p0-0.9.1.2.jar
    mysql-connector-java-5.1.7-bin.jar
    4)配置xml和db.properties
    <!--
        数据源
    -->
    <context:property-placeholder location="classpath:db.properties"/>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
# db.properties
jdbc.driver=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@127.0.0.1:1521:xe
jdbc.username=xxx
jdbc.password=xxx

3.一些基本的crud操作

update

作用:增删改
虽然我们使用了JDBCTemplate工具,但是一些基本的sql语句还是要写

public class Test {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-jdbc.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "insert into FYJ_JDBC_ORACLE_DEMO(ID,Name) values(?,?) ";
        jdbcTemplate.update(sql, "9", "zhangzhang");
    }
}

在这里插入图片描述

batchUpdate

作用:批量增删改

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-jdbc.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "insert into FYJ_JDBC_ORACLE_DEMO(ID,Name) values(?,?) ";
//        jdbcTemplate.update(sql, "9", "zhangzhang");
        List<Object[]> list = new ArrayList<>();
        list.add(new Object[]  {"10","lifugui"});
        list.add(new Object[]  {"11", "duoyu"});
        list.add(new Object[]  {"12", "666"});
        jdbcTemplate.batchUpdate(sql, list);
    }

以数组的形式将批量的数据存入list
在这里插入图片描述

queryForObject()

作用:
1.查询单行数据,返回一个对象

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-jdbc.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "select id,name from FYJ_JDBC_ORACLE_DEMO where name = ?";
        RowMapper<dataSource> rowMapper = new BeanPropertyRowMapper<>(dataSource.class);
        dataSource dataSource = jdbcTemplate.queryForObject(sql, rowMapper, "duoyu");
        System.out.println(dataSource);
    }
    //output:dataSource{ID='11', Name='duoyu'}

2.查询单值,返回单个值

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-jdbc.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql1 = "select count(id) from FYJ_JDBC_ORACLE_DEMO";
        Integer result = jdbcTemplate.queryForObject(sql1, Integer.class);
        System.out.println(result);
        //output:11

query:

查询多个数据到集合中返回回来

public class query {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-jdbc.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql = "select id,name from FYJ_JDBC_ORACLE_DEMO where id = ?";
        RowMapper<dataSource> rowMapper = new BeanPropertyRowMapper<>(dataSource.class);
        List<dataSource> list = jdbcTemplate.query(sql, rowMapper,9);
        System.out.println(list);
    }
    /**
     * [dataSource{ID='9', Name='Judy'}, 
     * dataSource{ID='9', Name='Judy'}, 
     * dataSource{ID='9', Name='zhangzhang'}, 
     * dataSource{ID='9', Name='zhangzhang'}]
     */
}

4.使用带有具名参数的JdbcTemplate

我们平时在jdbcTemplate中写sql的时候value 的值经常是一个"?“来作待确认值,现在可以通过”:x"的形式来具体的表示那个被代替的属性值。

<1>配置到IOC容器

<!--NamedParameterJdbcTemplate-->
    <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <!--这个东西无法自己选择构造器,要自己手动添加一个无参构造器-->
        <constructor-arg ref="dataSource"/>
    </bean>

<2>书写一个测试类

以插入为例:
第一种方法

public class namedParameter {
    /**
     * 测试具名模板类
     */
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-jdbc.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean("jdbcTemplate", JdbcTemplate.class);
        NamedParameterJdbcTemplate namedParameterJdbcTemplate =
                ctx.getBean("namedParameterJdbcTemplate", NamedParameterJdbcTemplate.class);
        String sql = "insert into FYJ_JDBC_ORACLE_DEMO(id,name) values(:id,:name)";
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("id", "15");
        map.put("name", "李四");
        namedParameterJdbcTemplate.update(sql, map);
        //因为里面所填参数的第二个值要求是以map的形式进行insert的填充,所以要手动创建一个map填充值
    }
}

第二种方法(好处在于不用封装map,第一种方法弊端在于只能插入少量数据)

public class namedParameterDao {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-jdbc.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean("jdbcTemplate", JdbcTemplate.class);
        NamedParameterJdbcTemplate namedParameterJdbcTemplate =
                ctx.getBean("namedParameterJdbcTemplate", NamedParameterJdbcTemplate.class);
        //模拟Service层直接传递给Dao层一个具体的对象
        dataSource dataSource = new dataSource("16", "张三丰");
        //在dao的插入方法中
        String sql = "insert into FYJ_JDBC_ORACLE_DEMO(Id,Name) values(:ID,:Name)";
        //将有参构造方法实例的参数对应的通过这个实例的类置入到表中
        SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(dataSource);
        namedParameterJdbcTemplate.update(sql, parameterSource);
    }
}

五、声明式事务管理

1.事务概述

  1. 在JavaEE企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。
  2. 事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行。
  3. 事务的四个关键属性(ACID)
    ①原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
    ②一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
    ③隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。
    ④持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。

2.写一个Bookshop的案例

在这里插入图片描述

<1>创建表,并且给表添加属性

CREATE TABLE book (
  isbn VARCHAR (50) PRIMARY KEY,
  book_name VARCHAR (100),
  price INT
) ;

CREATE TABLE book_stock (
  isbn VARCHAR (50) PRIMARY KEY,
  stock INT,
) ;

CREATE TABLE account (
  username VARCHAR (50) PRIMARY KEY,
  balance INT,
) ;

INSERT INTO account (`username`,`balance`) VALUES ('Tom',100000);
INSERT INTO account (`username`,`balance`) VALUES ('Jerry',150000);

INSERT INTO book1 (`isbn`,`book_name`,`price`) VALUES ('ISBN-001','book01',100);
INSERT INTO book1 (`isbn`,`book_name`,`price`) VALUES ('ISBN-002','book02',200);
INSERT INTO book1 (`isbn`,`book_name`,`price`) VALUES ('ISBN-003','book03',300);
INSERT INTO book1 (`isbn`,`book_name`,`price`) VALUES ('ISBN-004','book04',400);
INSERT INTO book 1(`isbn`,`book_name`,`price`) VALUES ('ISBN-005','book05',500);

INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-001',1000);
INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-002',2000);
INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-003',3000);
INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-004',4000);
INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-005',5000);

2.搭建JDBCTemplate和扫描注解的环境

        <context:component-scan base-package="tx"></context:component-scan>
    <!--
        数据源
    -->
    <context:property-placeholder location="classpath:db.properties"/>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!--JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

3.创建各种关系接口和类

public interface Cashier {
    //结账,同时购买多本书
    public void checkOut(String username, List<String>isbns);
}
@Service
public class CashierIml implements Cashier {

    @Autowired
    private BookShopService bookShopService;



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

}
public interface BookShopService {
    public void buybook(String username ,String isbn);
}
@Service
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDAO bookShopDAO ;
    public void buybook(String username, String isbn) {
//        买书要先去找书的价格
        Integer price = bookShopDAO.findPriceByIsbn(isbn);
//        再去看书的库存
        bookShopDAO.updateStock(isbn);
//        最后根据bookshopDAO进行针对人的扣款
        bookShopDAO.updateUserAccount(username,price);
    }
}
interface BookShopDAO {
    //根据书号查询价格
    public int findPriceByIsbn(String isbn);

    //更新书的库存
    public void updateStock(String isbn);

    //更新用户的余额
    public void updateUserAccount(String username, Integer price);
}
@Repository
public class BookShopDAOImpl implements BookShopDAO {

    @Autowired
    private JdbcTemplate jdbcTemplate;

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

    }

    @Override
    public void updateStock(String isbn) {
//        判断库存是否足够
        String sql = "select stock from book_stock where isbn =?";
        Integer stock = jdbcTemplate.queryForObject(sql, Integer.class, isbn);

        if(stock<=0){
            throw new RuntimeException("库存不足");
        }
        sql = "update book_stock set stock = stock - 1 where isbn = ?";
        jdbcTemplate.update(sql, isbn);
    }

    @Override
    public void updateUserAccount(String username, Integer price) {
        //判断余额是否足够
        String sql = "select balance from account where username = ?";
        Integer balance = jdbcTemplate.queryForObject(sql, Integer.class);

        if(balance < price ){
            throw new RuntimeException("余额不足");
        }
        sql = "update account set balance = ? where username = ? ";
        jdbcTemplate.update(sql, price, username);
    }
}

4.创建测试类

public class TransactionTest {
    private BookShopService bookShopService;
    private BookShopDAO bookShopDAO;

    @Before
    public void init() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-tx.xml");
        bookShopDAO = ctx.getBean("bookShopDAOImpl", BookShopDAO.class);
        bookShopService = ctx.getBean("bookShopServiceImpl", BookShopService.class);

    }

    @Test
    public void textTx() {
        bookShopService.buybook("Tom","ISBN-003");
    }
}
public interface Cashier {
    //结账,同时购买多本书
    public void checkOut(String username, List<String>isbns);
}

这就是一个我们平时写的一个普通的AOP项目,把买书的事情分成判断书的剩余量,金额是否可以买,通过判断书名对应书的金额,放在同一个切面进行同时判断。

但是在这里作者也遇到了一个bug,我用的是oracle ,已经创建好了表并且提交了数据,但是用queryForObject去搜索isbn的时候却显示值为空,报了EmptyResultDataAccessException的bug,后来解决bug的方法是重启oracle sql development,我也不明白,为什么数据提交了还检测不到,要重启数据库才能检测到。

5.创建事务管理器和事务注解

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

    <!-- 开启事务注解
        transaction-manager 用来指定事务管理器, 如果事务管理器的id值 是 transactionManager,
                                                       可以省略不进行指定。
    -->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
</beans>

由于事务管理要管理数据源,所以要ref链接到数据源的地址
然后在购买的方法上添加一个@Transactional这样就会防止余额不足还扣款的问题了

注解原理:spring扫描到这个方法的时候也能扫描到这个注解,说明spring明白它是被事务所作用的,而管理它的事务管理器对这个方法生成了一个代理,动态管理dataSource,这里可以用一个输出语句,输出类的地址来看,的确是一个proxy。但是如果不加注解的话,就是一个普通的方式,也不是代理了。

5.事务的传播

  • 事务的传播行为:一个事务方法被另一个事务方法调用的时候,当前的事务如何使用事务
    1.Propagation.REQUIRED默认值 使用调用者的事务
    2.Propagation.REQUIRES_NEW 将调用者的事务挂起,重新开启事务来使用
  • 事务的隔离级别 isolation:
    1 读未提交 脏读
    2 读已提交 不可重复读
    4 可重复读 幻读
    8 串行化 效率低
  • 事务的回滚与不回滚
    默认情况下,Spring会对所有运行时异常进行事务回滚
    rollbackFor
    rollbackForClassName
    noRollbackFor
    noRollbackForClassName
  • 事务的只读设置
    readOnly
    true: 只读 代表只对数据库进行读取操作,不会有修改的操作
    如果确保当前的事务只有读取操作,就有必要设置为只读,可以帮助数据库引擎优化业务
    false 非只读 不仅会读取,还会修改
  • 事务的超时设置:设置事务在强制回滚之前可以占用的时间
    timeout
    }
    @Transactional(
            propagation = Propagation.REQUIRES_NEW,
            isolation = Isolation.READ_COMMITTED,
            noRollbackFor = {},
            readOnly = false,
            timeout = 3)//只对当前方法起作用
    public void buybook(String username, String isbn) {
//        买书要先去找书的价格
        Integer price = bookShopDAO.findPriceByIsbn(isbn);
//        再去看书的库存
        bookShopDAO.updateStock(isbn);
//        最后根据bookshopDAO进行针对人的扣款
        bookShopDAO.updateUserAccount(username,price);
    }
}

猜你喜欢

转载自blog.csdn.net/qq_41936805/article/details/86571746