Spring框架

第一章 JDBCTemplate

1.1 jdbcTemplate介绍

由Spring框架给我们提供,Spring提供了很多操作数据源(关系型数据库,二维表格模型,有明确的行和列(mysql\oracle等)、非关系型数据库(redis、mongodb)NoSql、消息队列(activeMq、jms))的小工具,和Apache的dbUtils功能类似,属于Spring家族中的一员。

  • 使用方法和QueryRunner基本一致
  • 构造方法传递数据源DataSource对象
  • API方法
    • update(String sql, Object…obj)执行insert,update,delete语句
    • queryForObject(String sql,RowMapper mapper,Object…obj)查询返回单个对象
    • queryForObject(String sql,Class cla,Object…obj)查询返回单个对象,基本类型及其包装类和字符串
    • query(String sql,RowMapper mapper,Object…obj)查询返回集合对象
  • RowMapper接口实现类BeanPropertyRowMapper,查询的结果集封装,适用单个对象或者集合

1.2 jdbcTemplate实现数据表CRUD

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-jdbc</artifactId>
  4. <version>5.2.6.RELEASE</version>
  5. </dependency>
  • applicationContext.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
">
    <context:component-scan base-package="com.atguigu"></context:component-scan>

    <!-- 配置数据源,数据库连接池-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 注入DriverManagerDataSource类的属性-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!-- 配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 构造方法注入-->
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>
</beans>
  • dao层
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    //注入JdbcTemplate对象
    @Autowired
    @Qualifier("jdbcTemplate")
    private JdbcTemplate jdbcTemplate ;

    //保存账户
    @Override
    public int saveAccount(Account account) throws SQLException {
        String sql = "insert into account values(?,?,?)";
        return jdbcTemplate.update(sql,null,account.getName(),account.getMoney());
    }

    //更新账户
    @Override
    public int updateAccountById(Account account) throws SQLException {
        String sql = "update account set name = ? , money = ? where id = ?";
        return jdbcTemplate.update(sql,account.getName(),account.getMoney(),account.getId());
    }

    //删除账户
    @Override
    public int deleteAccountByid(int id) throws SQLException {
        String sql = "delete from account where id = ? ";
        return jdbcTemplate.update(sql,id);
    }

    //id查询账户
    @Override
    public Account queryAccountById(int id) throws SQLException {
        String sql = "select id,name,money from account where id = ?";
        return jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<Account>(Account.class),id);
    }

    //查询全部账户
    @Override
    public List<Account> queryAccountByList() throws SQLException {
        String sql = "select id,name,money from account";
        return jdbcTemplate.query(sql,new BeanPropertyRowMapper<Account>(Account.class));
    }

    //查询账户总条数
    @Override
    public int queryAccountByCount() throws SQLException {
        String sql = "select count(id) from account";
        return jdbcTemplate.queryForObject(sql,int.class);
    }

    //自定义RowMapper,pojo对象属性和数据表不对应
    @Override
    public List<Account> queryAccountByAsName() throws SQLException {
        String sql = "select id ids,name names,money moneys from account";
        return jdbcTemplate.query(sql, new RowMapper<Account>(){
            public Account mapRow(ResultSet resultSet,int row)throws SQLException{
                Account account = new Account();
                account.setId( resultSet.getInt("ids") );
                account.setName( resultSet.getString("names"));
                account.setMoney( resultSet.getDouble("moneys"));
                return  account;
            }
        });

    }
}
  • service层
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    @Qualifier("accountDao")
    private AccountDao accountDao ;

    @Override
    public int saveAccount(Account account) throws SQLException {
        return accountDao.saveAccount(account);
    }

    @Override
    public int updateAccountById(Account account) throws SQLException {
        return accountDao.updateAccountById(account);
    }

    @Override
    public int deleteAccountById(int id) throws SQLException {
        return accountDao.deleteAccountByid(id);
    }

    @Override
    public Account queryAccountById(int id) throws SQLException {
        return accountDao.queryAccountById(id);
    }

    @Override
    public List<Account> queryAccountByList() throws SQLException {
        return accountDao.queryAccountByList();
    }

    @Override
    public int queryAccountByCount() throws SQLException {
        return accountDao.queryAccountByCount();
    }

    @Override
    public List<Account> queryAccountByAsName() throws SQLException {
        return accountDao.queryAccountByAsName();
    }
}
  • 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MainTest {

    @Autowired
    @Qualifier("accountService")
    private AccountService service;

    @Test
    public void testSaveAccount()throws SQLException{
        Account account = new Account();
        account.setName("老王");
        account.setMoney(1200);
        service.saveAccount(account);
    }

    @Test
    public void testUpdateAccountById()throws SQLException{
        Account account = new Account();
        account.setId(7);
        account.setName("老王2");
        account.setMoney(1220);
        service.updateAccountById(account);
    }

    @Test
    public void testDeleteAccountById()throws SQLException{
        service.deleteAccountById(7);
    }

    @Test
    public void testQueryAccountById()throws SQLException{
        Account account = service.queryAccountById(2);
        System.out.println(account);
    }

    @Test
    public void testQueryAccountByCount() throws SQLException{
        int count = service.queryAccountByCount();
        System.out.println(count);
    }

    @Test
    public void testQueryAccountByList() throws SQLException{
        List<Account> accountList = service.queryAccountByList();
        if (accountList != null && accountList.size() > 0){
            for(Account account : accountList){
                System.out.println(account);
            }
        }
    }

    @Test
    public  void testQueryAccountByListAsName() throws SQLException {
        List<Account> accountList = service.queryAccountByAsName();
        if (accountList != null && accountList.size() > 0){
            for(Account account : accountList){
                System.out.println(account);
            }
        }
    }
}

1.3 继承JdbcDaoSupport类

JdbcDaoSupport类中定义了JdbcTemplate类,我们dao层类直接继承即可,自己无需在声明JdbcTemplate对象,但是不能使用注解方式注入了。

  • dao层改进
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

   /* @Autowired
    @Qualifier("jdbcTemplate")
    private JdbcTemplate jdbcTemplate ;*/


    @Override
    public int saveAccount(Account account) throws SQLException {
        String sql = "insert into account values(?,?,?)";
        return getJdbcTemplate().update(sql,null,account.getName(),account.getMoney());
    }

    @Override
    public int updateAccountById(Account account) throws SQLException {
        String sql = "update account set name = ? , money = ? where id = ?";
        return getJdbcTemplate().update(sql,account.getName(),account.getMoney(),account.getId());
    }

    @Override
    public int deleteAccountByid(int id) throws SQLException {
        String sql = "delete from account where id = ? ";
        return getJdbcTemplate().update(sql,id);
    }

    @Override
    public Account queryAccountById(int id) throws SQLException {
        String sql = "select id,name,money from account where id = ?";
        return getJdbcTemplate().queryForObject(sql,new BeanPropertyRowMapper<Account>(Account.class),id);
    }

    @Override
    public List<Account> queryAccountByList() throws SQLException {
        String sql = "select id,name,money from account";
        return getJdbcTemplate().query(sql,new BeanPropertyRowMapper<Account>(Account.class));
    }

    @Override
    public int queryAccountByCount() throws SQLException {
        String sql = "select count(id) from account";
        return getJdbcTemplate().queryForObject(sql,int.class);
    }

    @Override
    public List<Account> queryAccountByAsName() throws SQLException {
        String sql = "select id ids,name names,money moneys from account";
        return getJdbcTemplate().query(sql, new RowMapper<Account>(){
            public Account mapRow(ResultSet resultSet,int row)throws SQLException{
                Account account = new Account();
                account.setId( resultSet.getInt("ids") );
                account.setName( resultSet.getString("names"));
                account.setMoney( resultSet.getDouble("moneys"));
                return  account;
            }
        });

    }
}
  • applicationContext.xml
<bean id = "accountDao" class="com.atguigu.dao.AccountDaoImpl">
    <property name="dataSource" ref="dataSource"></property>
</bean>

第二章 Spring事务控制

1.1 事务的控制方式

Spring的事务机制是Spring给我们提供的一套事务管理的方式,项目中我们就不需要手动去控制事务了

编程式事务:我们的事务控制逻辑(增强逻辑)和业务逻辑混合在一起,比如我们之前的tcf模式控制转账事务,这种方式就叫做编程式事务.

声明式事务:通过配置,在不侵犯原有业务逻辑代码的基础上就添加了事务控制功能,这种方式叫做声明式事务(我们这里的Spring声明式事务控制就是通过AOP达到这个目的的)

1.2 事务的ACID特性

  • 事务基本特性(ACID,是针对单个事务的一个完美状态)
    • 原子性:一个事务内的操作,要么都成功,要么都失败。很经典的例子:转账,汇款和收款要成功都成功,要失败都失败。
    • 一致性:指的是数据的一致性,和原子性其实是一件事情,只不过描述的角度不一样,原子性是从事务的操作的角度,一致性是从数据的角度来描述的,比如转账之前(1000,1000),如果转账100,那么数据状态应该是(900、1100),不应该出现中间状态(900,1000)或者(1000,1100)
    • 隔离性:事务并发的时候,比如事务1做的动作给员工涨工资2000块,但是此时事务还没有提交,事务2去查询工资发现工资多了2000块,这就是脏读。解决方法就是建立事务之间的隔离机制。
    • 持久性:事务一旦提交,事务提交,变化即生效。即使数据库服务器宕机,那么恢复之后,数据也应该是事务提交之后的状态,不应该回滚到以前了。
  • 事务并发问题
    • 脏读
      财务人员今天心情不好,状态不好,误操作发起事务1给员工张三本月涨了1w块钱工资,但是还没有提交事务
      张三发起事务2,查询当月工资,发现多了1W块钱,涨工资了,财务人员发现不对劲,把操作撤回,把涨工资的事务1给回滚了
    • 幻读(幻读出现在增加insert和删除delete的时候)
      • 比如事务1查询工资表中工资为1w的员工的个数(10个员工),此时事务1还没有结束
      • 正在这个时候,事务2,人力部门有两个新员工入职,他们的工资也是1w,人力部门通过事务2向工资表插入了两条记录,并且提交事务了
      • 这个时候,事务1又去查询工资为1w的员工个数,发现多了两个员工(12个人),见鬼了,这种情况就叫做幻读
    • 不可重复读(出现在修改update的时候)
      • 员工发起事务1查询工资,工资为1w,事务1尚未关闭
      • 人力部门发起事务2给你涨了工资,涨工资到1.2W(update你的工资表的字段信息),并且提交了事务了。
      • 此时,事务1又再次查询自己的工资,发现工资为1.2W,原有的1w这个数据已经读不到了,这就叫做不可重复读
    • 事务隔离级别(解决是事务并发问题的)
      • 极端模式:读未提交 Read_uncommited,就好比十字路口没有红绿灯一样,效率高,但是风险也高,此时什么事务控制都没有。不要使用这种模式
      • 读已提交 Read_commited,顾名思义,其他事务提交之后,才能读取到这个事务提交的数据,这种模式能解决脏读(因为脏读事务没提交造成的)问题,解决不了幻读和不可重复读(因为这两个问题的产生就是insert delete update的时候提交了事务造成)
      • 可重复读 Repeatable_Read,可重复读解决脏读和不可重复读
      • 极端模式:串行化:所有的事务一个个来,不争不抢,一个事务处理完了,另外一个事务继续进行,这样不会出现并发问题。比如ATM机
      • 默认:DEFAULT,默认是数据库的默认,默认模式来源于上面四种模式之一,mysql数据库默认隔离级别可重复读Repeatable_Read,oracle数据库默认级别读已提交Read_commited
      • 设置事务隔离级别
        • 1 read uncommitted 未提交读,脏读,不可重复读,虚读都可能发生.
        • 2 read committed 已提交读,避免脏读,但是不可重复读和虚读有可能发生(Oracle默认)
        • 4 repeatable read 可重复读,避免脏读,不可重复读,但是虚读有可能发生(MySql默认)
        • 8 serializable 串行化的,避免脏读,不可重复读,虚读的发生
        • 查看当前的事务隔离级别:SELECT @@TX_ISOLATION;
        • 更改当前的事务隔离级别:SET TRANSACTION ISOLATION LEVEL 四个级别之一

1.3 事务传播行为

  • 我们的事务往往加载service层方法上,那么我们现在的业务简单些,直接service调用dao层方法,以后可能涉及service层方法A()直接调用service层方法B()。那么此时A()和B()都有自己的事务控制,那么相互调用的时候就会有问题啊,A和B应该有一个关于事务的协商机制,这种机制就叫做事务的传播行为
  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
  • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
  • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 NEVER:以非事务方式运行

1.4 Spring的事务管理器

PlatformTransactionManager接口

  • 实现类:org.springframework.jdbc.datasource.DataSourceTransactionManager 使用Spring JDBC或MyBatis 进行持久化数据时使用
  • 实现类:org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate版本进行持久化数据时使用

1.5 半注解半xml形式的事务管理

  • 事务配置<tx:advice>通知标签
    • 属性id:自定义唯一表示
    • transaction-manager属性:事务管理类,配置事务管理类的id属性值
  • 事务属性配置<tx:attributes>子标签
    • <tx:method>事务方法标签
      • 属性name:方法名
      • 属性read-only:是否只读事务,查询都是只读,其他是非只读
      • 属性propagation:事务的传播行为,默认配置REQUIRED或者SUPPORTS
      • 属性isolation:事务隔离级别,默认配置DEFAULT
      • 属性timeout:事务超时时间,配置-1
      • 属性no-rollback-for:遇到什么异常不回滚,配置异常类名,多个类逗号分开
      • 属性rollback-for:遇到什么异常回滚
        • 以上回滚属性不配置,遇到异常就回滚
  • aop切面配置<aop:config>标签
    • <aop:advisor>子标签
      • 属性advice-ref:引用通知,配置tx:advice标签的属性值
      • 属性pointcut:切点配置
  • applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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: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
       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

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

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>

    <!-- spring的声明式事务  AOP实现,事务管理类DataSourceTransactionManager-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 事务通知配置-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 事务属性配置-->
        <tx:attributes>
            <!-- 非只读事务-->
            <tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"></tx:method>
            <!-- 查询方法,query开头方法名,配置只读事务-->
            <tx:method name="query*" read-only="true" propagation="SUPPORTS"></tx:method>
        </tx:attributes>
    </tx:advice>
    <!-- aop切面配置-->
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.service.*.*(..))"></aop:advisor>
    </aop:config>
</beans>

1.5 纯注解形式的事务管理

  • @Transactional注解,取代tx标签
  • @EnableTransactionManagement注解,开启事务注解
  • 业务层
@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {

    @Autowired
    @Qualifier("accountDao")
    private AccountDao accountDao;

    @Override
    public void transfer(String fromName, String toName, double money) throws SQLException {
        Account accountFormName = accountDao.queryAccountByName(fromName);
        Account accountToName = accountDao.queryAccountByName(toName);
        accountFormName.setMoney( accountFormName.getMoney() - money );
        accountToName.setMoney( accountToName.getMoney() + money);
        accountDao.updateAccount(accountFormName);
        int a = 1/0;
        accountDao.updateAccount(accountToName);
    }
}
  • SpringConfig类
@Configuration
@EnableTransactionManagement
@ComponentScan("com.atguigu")
@PropertySource("classpath:db.properties")
public class SpringConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean("dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName(driver);
        driverManagerDataSource.setUrl(url);
        driverManagerDataSource.setUsername(username);
        driverManagerDataSource.setPassword(password);
        return driverManagerDataSource;
    }

    @Bean("jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(@Qualifier("dataSource") DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager createTransactionManager(@Qualifier("dataSource") DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}
  • 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class MainTest_Anno {

    @Autowired
    @Qualifier("accountService")
    private AccountService service;

    @Test
    public void testAnnotation() throws SQLException {
        service.transfer("张三","李四",100);
    }
}