spring 框架学习-4

spring 框架学习安排

1. 第四天

1. spring中的JdbcTemplate

1.JdbcTemplate的CRUD基本使用
  1. JdbcTemplate的作用:与数据库进行交互,进行CRUD操作
  2. 最简单的demo
public class JDBCTemplateDemo1 {

    public static void main(String[] args) {
//        使用spring自带的数据源
        DriverManagerDataSource dmds = new DriverManagerDataSource();
        dmds.setDriverClassName("com.mysql.jdbc.Driver");
        dmds.setUsername("root");
        dmds.setPassword("809080");
        dmds.setUrl("jdbc:mysql://localhost:3306/eesy");
        JdbcTemplate template = new JdbcTemplate(dmds);
        template.execute("insert into account(name,money)values('yyy',4000)");
    }
}
  1. 发现问题,有多个setXXX的出现,可以使用IOC来注入,添加bean.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">
    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="ds"></property>
    </bean>

    <!--配置datasource-->
    <bean id="ds" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="username" value="root"></property>
        <property name="password" value="809080"></property>
    </bean>
</beans>

重新测试上述demo

/**
 * Created by liuzeyu on 2020/4/25.
 * 使用spring IOC
 */
public class JDBCTemplateDemo2 {

    public static void main(String[] args) {
        //使用IOC
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
        jdbcTemplate.execute("delete from account where name like '%y%'");
    }
}
  1. 进行CRUD操作
/**
 * Created by liuzeyu on 2020/4/25.
 * JdbcTemplate 的 GRUD操作
 */
public class JDBCTemplateDemo3 {

    public static void main(String[] args) {
        //使用IOC
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
        //1. 对数据库插入一条数据
        //jdbcTemplate.update("insert into account (name,money)values(?,?)","liuzeyu",34444);
        //2. 修改一条数据
        //jdbcTemplate.update("update account set money=8888 where name=?","liuzeyu");
        //3. 删除一条数据
        //jdbcTemplate.update("delete from account where name like ?","%liuz%");
        //4. 查询所有
        //List<Account> accounts = jdbcTemplate.query("select * from account where money > ?", new subMapper(), 100);
//        List<Account> accounts = jdbcTemplate.query("select * from account where money > ?",
//                new BeanPropertyRowMapper<Account>(Account.class), 100);
//        for (Account account : accounts) {
//            System.out.println(account);
//        }
        //5. 查询一个
//        List<Account> query = jdbcTemplate.query("select * from account where money > ?",
//                new BeanPropertyRowMapper<Account>(Account.class), 1000);
//        System.out.println(query.isEmpty() ? "查询为空" :query.get(0));
        //6. 查询一个
        Integer count = jdbcTemplate.queryForObject("select count(*) from account where money >?", Integer.class, 0);
        System.out.println(count);

    }
}

class subMapper implements RowMapper{

    public Account mapRow(ResultSet resultSet, int i) throws SQLException {
        Account account = new Account();
        account.setId(resultSet.getInt("id"));
        account.setMoney(resultSet.getFloat("money"));
        account.setName(resultSet.getString("name"));
        return account;
    }
}

其中

所以我们通常使用spring自带的

new BeanPropertyRowMapper<T>(T.class)
2.JdbcTemplate的DAO层使用
  1. dao层代码
public interface IAccountDao {

    //根据id查询
    public Account findAccountById(Integer id);

    //根据name查询
    public Account findAccountByName(String name);

}
public class AccountDaoImpl implements IAccountDao {
    private JdbcTemplate jdbcTemplate = null;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public Account findAccountById(Integer id) {
        List<Account> accounts = jdbcTemplate.query("select * from account where id=?",
                new BeanPropertyRowMapper<Account>(Account.class), id);
        Account  account = accounts.isEmpty() ?null:accounts.get(0);
        return account;
    }

    public Account findAccountByName(String name) {
        List<Account> accounts = jdbcTemplate.query("select * from account where name =?",
                new BeanPropertyRowMapper<Account>(Account.class), name);
        if(accounts.isEmpty()){
            return null;
        }
        if(accounts.size() > 1){
            throw new RuntimeException("查询结果不唯一");
        }
        return accounts.get(0);
    }

}

  1. bean.xml添加jdbcTemplate注入
<?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">
    <!--配置accountDao-->
    <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

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

    <!--配置datasource-->
    <bean id="ds" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="username" value="root"></property>
        <property name="password" value="809080"></property>
    </bean>
</beans>
  1. 测试方法
public class JDBCTemplateDemo4 {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountDao accountDao = ac.getBean("accountDao", IAccountDao.class);
        Account byId = accountDao.findAccountById(1);
        //System.out.println(byId);
        Account byName = accountDao.findAccountByName("ccc");
        //System.out.println(byName);
    }
}
3. Spring提供JdbcDaoSupport的支持

2中Dao层存在的问题:如果有多个dao,则下面代码将重复出现多次

如何解决这一个问题呢?我们可以定义一个类,并把重复的代码抽取出来

public class DaoSupport {

    public JdbcTemplate jdbcTemplate = null;

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate = new JdbcTemplate();
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void setDataSource(DataSource source){
        if(jdbcTemplate == null)
            jdbcTemplate = new JdbcTemplate(source);
    }
    public JdbcTemplate createJdbcTemplate(DataSource source) {
        return new JdbcTemplate(source);
    }

}


那如何获取这个datasource呢?可以注入
因为dao的实现类继承了该方法,那它一定继承了该方法的setDataSource方法,于是可以注入

<?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">
    <!--配置accountDao-->
    <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl">
        <!--<property name="jdbcTemplate" ref="jdbcTemplate"></property>-->
        <property name="dataSource" ref="ds"></property>
    </bean>

    <!--配置JdbcTemplate已经不需要了-->
    <!--<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">-->
        <!--<property name="dataSource" ref="ds"></property>-->
    <!--</bean>-->

    <!--配置datasource-->
    <bean id="ds" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="username" value="root"></property>
        <property name="password" value="809080"></property>
    </bean>
</beans>

dao层实现类的使用可以直接super.jdbcTemplate得到
当然。spring这么强大,已经为我们提供了一个DaoSupport 来处理多处编写jdbcTemplate的问题。也是可以以上面注入的相同方式得到jdbcTemplate

public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao 

2. 作业:spring中基于AOP的事务控制

github链接
说明:注解配置和老师的演示结果有所不同,后期又必要深入研究。

4. spring中的事务控制

1. 基于XML的声明式事务控制
  1. 准备工程

    1. dao层代码
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 根据Id查询账户
         * @param accountId
         * @return
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 根据名称查询账户
         * @param accountName
         * @return
         */
        Account findAccountByName(String accountName);
    
        /**
         * 更新账户
         * @param account
         */
        void updateAccount(Account account);
    }
    
    /**
     * 账户的持久层实现类
     */
    public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
    
    
        public Account findAccountById(Integer accountId) {
            List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
            return accounts.isEmpty()?null:accounts.get(0);
        }
    
    
        public Account findAccountByName(String accountName) {
            List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
            if(accounts.isEmpty()){
                return null;
            }
            if(accounts.size()>1){
                throw new RuntimeException("结果集不唯一");
            }
            return accounts.get(0);
        }
    
        public void updateAccount(Account account) {
            super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        }
    }
    
    
    1. service层代码
    package com.liuzeyu.service;
    
    import com.liuzeyu.domain.Account;
    
    /**
     * 账户的业务层接口
     */
    public interface IAccountService {
        /**
         * 根据id查询账户信息
         * @param accountId
         * @return
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 转账
         * @param sourceName    转成账户名称
         * @param targetName    转入账户名称
         * @param money         转账金额
         */
        void transfer(String sourceName, String targetName, Float money);
    }
    
    
    /**
     * 账户的业务层实现类
     *
     * 事务控制应该都是在业务层
     */
    public class AccountServiceImpl implements IAccountService{
    
        private IAccountDao accountDao;
    
        public void setAccountDao(IAccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
    
        public Account findAccountById(Integer accountId) {
            return accountDao.findAccountById(accountId);
    
        }
        public void transfer(String sourceName, String targetName, Float money) {
            System.out.println("transfer....");
                //2.1根据名称查询转出账户
                Account source = accountDao.findAccountByName(sourceName);
                //2.2根据名称查询转入账户
                Account target = accountDao.findAccountByName(targetName);
                //2.3转出账户减钱
                source.setMoney(source.getMoney()-money);
                //2.4转入账户加钱
                target.setMoney(target.getMoney()+money);
                //2.5更新转出账户
                accountDao.updateAccount(source);
    
                int i = 1/0;
    
                //2.6更新转入账户
                accountDao.updateAccount(target);
        }
    }
    
    
    1. 实体类
    package com.liuzeyu.domain;
    
    import java.io.Serializable;
    
    /**
     * 账户的实体类
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Float getMoney() {
            return money;
        }
    
        public void setMoney(Float money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    }
    
    
    1. pom.xml & bean.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.liuzeyu12a</groupId>
        <artifactId>day04_spring_txXML</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
        </dependencies>
    
    </project>
    
    <?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">
        <!--配置accountDao-->
        <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl">
            <property name="dataSource" ref="ds"></property>
        </bean>
    
        <bean id="accountService" class="com.liuzeyu.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!--配置datasource-->
        <bean id="ds" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
            <property name="username" value="root"></property>
            <property name="password" value="809080"></property>
        </bean>
    </beans>
    
    1. 测试函数
    /**
     * 使用Junit单元测试:测试我们的配置
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:bean.xml")
    public class AccountServiceTest {
    
        @Autowired
        private  IAccountService as;
    
        @Test
        public  void testTransfer(){
            as.transfer("aaa","bbb",100f);
    
        }	
    }
    
  2. 开始配置基于XML的声明式事务控制

    1. 需要导入aspect的依赖
     <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjweaver</artifactId>
         <version>1.8.7</version>
     </dependency>
    
    1. 在原有的bean.xml基础上配置事务
    <!--spring中基于XML的声明式事务控制(我们不用再自己写事务管理器类和方法)
        1.配置事务管理器
        2.配置事务管理器通知(此时我们需要引入事务管理器约束+tx名称空间和约束)同时也需要aop
            使用tx:advice配置事务管理器通知
                属性:
                    id:给事务通知起一个唯一标识
                    transaction:给事务通知引用一个事务管理器
            3.配置aop的通用切入点表达式
    -->
    <!--1.配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="ds"></property>
    </bean>
    <!--2.配置事务管理器通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--2.1配置事务的属性
             isolation="" :用于指定事务的隔离级别。默认为default,表示使用数据库的隔离级别
             rollback-for="":表示用于指定一个异常,当产生该异常时,回滚事务,产生其它异常时,事务不回滚。没有默认值表示任何异常都回滚。
             no-rollback-for="":表示用于指定一个异常,当产生该异常时,不回滚事务,产生其它异常时,事务回滚,没有默认值表示任何异常都回滚。
             propagation="":用于指定事务的传播行为。默认值时REQUIRED,用于增删改的选择。查询方法使用SUPPORT
             read-only="":用于指定事务是否只读,只有查询方法才能涉及为true,默认为false,表示读写
             timeout="":用于指定事务的超时时间,默认为-1表示永不超时,如果指定了时间,以秒为单位
        -->
    
        <tx:attributes>
            <!--增删改-->
            <tx:method name="transfer" propagation="REQUIRED" read-only="false"/>
            <!--查询方法-->
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>
    
    <!--3.配置aop-->
    <aop:config>
        <!--3.1配置aop的通用切入点表达式-->
        <aop:pointcut id="pt" expression="execution(* com.liuzeyu.service.impl.*.*(..))"></aop:pointcut>
    
        <!--3.2建立切入点表达式和事务通知的对应关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
    </aop:config>
    
2. 基于注解的声明式事务控制(基于上个项目)
  1. 将dao层和service层的实习类加入IOC容器中
@Service("accountService")
@Transactional
public class AccountServiceImpl implements IAccountService{...}
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate template = null;

由于使用了注解配置,因此要想获取template的值只能从bean.xml重新将JdbcTemplate 添加到IOC容器中,然后使用@Autowired注入

  1. 修改bean.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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       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

        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--创建IOC容器要扫描的包-->
    <context:component-scan base-package="com.liuzeyu"></context:component-scan>

    <!--导入jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="ds"></property>
    </bean>

    <!--配置datasource-->
    <bean id="ds" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="username" value="root"></property>
        <property name="password" value="809080"></property>
    </bean>

    <!--spring中基于注解事务控制(我们不用再自己写事务管理器类和方法)
        1.配置事务管理器
        2.开启spring对注解事务的支持
    -->
    <!--1.配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="ds"></property>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
  1. 对业务层引入事务控制,添加注解@Transactional
@Service("accountService")
@Transactional
public class AccountServiceImpl implements IAccountService{...}

@Transactional表示属性去默认值,也可以加到方法上,但如果遇到不同的方法,我们必须指定不同的属性,于是需要配置多次,这是比xml配置繁琐的。

2. 基于纯注解的声明式事务控制
  1. 无需修改dao层和service层的代码,添加配置类
@Configuration
@ComponentScan(basePackages = "com.liuzeyu")  //扫描包
@EnableTransactionManagement   //开启事务管理
@Import(value = {JdbcConfiguration.class,TransactionConfiguration.class}) //子配置类
@PropertySource("jdbc.properties")   //引入外部资源文件
public class SpringConfiguration {
}
  1. 将上面的bean.xml配置文件使用注解进行配置

资源文件:

jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=809080
jdbc.driver=com.mysql.jdbc.Driver

JdbcConfiguration 子配置类

/**
 * Created by liuzeyu on 2020/4/26.
 */
public class JdbcConfiguration {

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

    @Bean("jdbcTemplate")//将JdbcTemplate对象加入到容器中
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    @Bean("dataSource") //将DriverManagerDataSource 对象加入到容器中
    public DataSource createDataSouruce(){
        DriverManagerDataSource dataSource  = new DriverManagerDataSource();
        dataSource.setUrl(url);
        dataSource.setPassword(password);
        dataSource.setUsername(username);
        dataSource.setDriverClassName(driver);
        return dataSource;
    }
}

事务管理类配置

public class TransactionConfiguration {

    @Bean("transactionManager")  //将PlatformTransactionManager对象加入到容器中
    public PlatformTransactionManager createTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}