JAVA学习笔记23——Spring

Spring

1. 概述

1.1 简介

Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 SpringMVC和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。

1.2 优势

方便解耦,简化开发
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
降低 JavaEE API 的使用难度
Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
Java 源码是经典学习范例
Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。

1.3 体系结构

在这里插入图片描述

2. IOC

2.1 程序的耦合

        //执行sql流程
        //注册驱动
        DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        //获取连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/springdb","root","123");
        //获取操作数据库的预处理对象
        PreparedStatement pstm = conn.prepareStatement("select * from account");
        //执行SQL,得到结果集
        ResultSet resultSet = pstm.executeQuery();
        //遍历结果集
        while (resultSet.next()){
    
    
            System.out.println(resultSet.getString("name"));
        }
        //释放资源
        resultSet.close();
        pstm.close();
        conn.close();

耦合:程序间的依赖关系
类之间的依赖,方法之间的依赖
解耦:降低程序间的依赖关系
实际开发中做到编译期不依赖,运行时才依赖
解决思路
第一步:使用反射创建对象避免使用new关键字
第二步:通过读取配置文件获取要创建的对象全限定类名

Bean:可重用组件。
一个创建Bean对象的工厂,用来创建我们的service和dao对象

2.2 工厂模式

在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
步骤
第一步:需要一个配置文件配置我们的service和dao
配置内容:唯一标识符-全限定类名(key-value)
第二步:通过读取配置文件中的配置的内容,反射创建对象
配置文件
xml或者properties
在这里插入图片描述
配置文件bean.properties

accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl

BeanFactory.java

//创建Bean对象的方法
//Bean:可重用组件
public class BeanFactory {
    
    
    //定义一个Properties对象
    private static Properties props;
    //定义一个容器Map用于存放我们要创建地对象
    private static Map<String,Object> beans;
    //使用静态代码块为Properties对象赋值
    static {
    
    
        try{
    
    
            //实例化对象
            props = new Properties();
            //获取properties文件的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
            //实例化容器
            beans = new HashMap<String,Object>();
            //取出配置文件中所有的key
            Enumeration keys = props.keys();
            //遍历枚举
            while (keys.hasMoreElements()){
    
    
                //取出每个key
                String key = keys.nextElement().toString();
                //根据key获取value
                String beanPath = props.getProperty(key);
                //反射创建对象
                Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();
                //把key与value存入容器
                beans.put(key,value);
            }
        } catch (Exception e){
    
    
            throw new ExceptionInInitializerError("初始化properties失败!");
        }
    }
    //根据Bean的名称获取bean对象
    public static Object getBean(String beanName){
    
    
        return beans.get(beanName);
    }
}
//业务层实现类
public class AccountServiceImpl implements IAccountService {
    
    

//    private IAccountDao accountDao = new AccountDaoImpl();
    private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
    @Override
    public void saveAccount() {
    
    
        accountDao.saveAccount();
    }
}
//模拟表现层,用于调用业务层
public class Client {
    
    
    public static void main(String[] args) {
    
    
//        IAccountService as = new AccountServiceImpl();
        IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
        as.saveAccount();
    }
}

2.3 Inversion Of Control控制反转

第一种方式

private IAccountDao accountDao = new AccountDaoImpl();

主动获取对象
在这里插入图片描述
第二种方式

private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");

被动获取对象
在这里插入图片描述
明确 ioc 的作用
削减计算机程序的耦合(解除我们代码中的依赖关系)。

3. 使用Spring的IOC解决程序耦合

3.1 入门案例

第一步:导入坐标

org.springframework spring-context 5.0.2.RELEASE

第二步:配置文件

<?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">
<!--把对象交给spring管理-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
</beans>

第三步:获取spring的IOC核心容器并根据id获取对象

public class Client {
    
    
    //获取spring的IOC核心容器并根据id获取对象
    public static void main(String[] args) {
    
    
        //获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //根据id获取bean对象(两种方式)
        IAccountService as = (IAccountService)ac.getBean("accountService");
        IAccountDao ad = ac.getBean("accountDao",IAccountDao.class);
        System.out.println(as);
        System.out.println(ad);
    }
}

3.2 ApplicationContext实现类

根据不同的方式读取配置文件
ClassPathXmlApplicationContext:加载类路径下的配置文件
FileSystemXmlApplicationContext:加载磁盘任意路径下的配置文件
AnnotationConfigApplicationContext:读取注解创建容器

3.3 核心容器两个接口引发的问题

ApplicationContext:在构建核心容器时,创建对象采取的策略时立即加载的方式,只要一读取完马上就创建配置文件中配置的对象。适用于单例模式
BeanFactory:在构建核心容器时,创建对象的策略采用延迟加载的方式,什么时候根据id获取对象,什么时候真正的创建对象。适用于多例对象

3.4 Spring对bean的管理细节

3.4.1 创建bean的三种方式

第一种方式:使用默认构造函数创建
在Spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建。此时如果类中没有默认构造函数,则无法创建

<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>

第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)

<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>

第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)

<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>

3.4.2 bean对象的作用范围

scope属性
作用:用于指定bean的作用范围
取值:
singleton:单例,默认值
prototype:多例
request:作用于web应用的请求范围
session:作用域web应用的会话范围
global-session:作用于集群环境的会话范围,当不是集群环境时,它就是session

3.4.3 bean对象的生命周期

单例对象
出生:当容器创建时
存活:容器存在,对象一直存活
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时Spring框架为我们创建
存活:对象只要在使用过程中一直存活
死亡:当对象长时间不使用时由Java垃圾回收器回收

3.5 依赖注入

3.5.1 概述

Dependency Injection
IOC作用:降低程序间的耦合(依赖关系)
依赖关系交给Spring维护。在当前类需要用到其他类的对象,由Spring为我们提供,我们只需要在配置文件中说明依赖关系的维护,称之为依赖注入。

依赖注入能注入的数据有三类
基本类型和String,其他bean类型,复杂类型(集合类型)

注入方式
第一种,使用构造函数
第二种,使用set方法
第三种,使用注解提供

3.5.2 使用构造函数注入

标签:constructor
标签出现位置:bean标签内部
标签属性
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型。但是不能实现独立注入

<!--假设有两个String类型的数据,看不出来注入哪个-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <constructor-arg type="java.lang.String" value="test"></constructor-arg>
    </bean>

index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引位置从0开始。可以独立实现功能
name:用于指定给构造函数中指定名称的参数赋值(常用)
value:用于提供基本型和String类型的数据
ref:用于指定其他的bean类型数据。它指的时在Spring的IOC核心容器中出现过的bean对象
优势:在获取bean对象时,注入数据时必须的操作,否则无法创建成功
弊端:改变了bean对象实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供

<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="Kobe"></constructor-arg>
        <constructor-arg name="age" value="24"></constructor-arg>
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>
<!--配置一个日期对象-->
    <bean id="now" class="java.util.Date"></bean>

3.5.2 使用set方法注入

标签:property
标签出现位置:bean标签内部
标签属性
name:用于指定注入是所调用的set方法名称
value:用于提供基本型和String类型的数据
ref:用于指定其他的bean类型数据。它指的时在Spring的IOC核心容器中出现过的bean对象

<!--set方法注入
    涉及标签:property-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="name" value="Kobe"></property>
        <property name="age" value="24"></property>
        <property name="birthday" ref="now"></property>
    </bean>
    <bean id="now" class="java.util.Date"></bean>

优势:创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:如果某个成员必须有值,则获取对象时有可能set方法没有执行

3.5.3 复杂类型的注入

 <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="myStr">
            <array>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </array>
        </property>
        <property name="myList">
            <list>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </list>
        </property>
        <property name="mySet">
            <set>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </set>
        </property>
        <property name="myMap">
            <map>
                <entry key="A" value="a"></entry>
                <entry key="B" value="b"></entry>
                <entry key="C" value="c"></entry>
            </map>
        </property>
        <property name="myProps">
            <props>
                <prop key="AAA">aaa</prop>
                <prop key="BBB">bbb</prop>
                <prop key="CCC">ccc</prop>

            </props>
        </property>
    </bean>

总结
用于给List结构集合注入的标签:list,array,set
用于给Map结构集合注入的标签:map,props
结构相同,标签可以互换

4. 基于注解的IOC配置

4.1 注解

4.1.1 用于创建对象

@Component,@Controller,@Service,@Repository
用于把当前类对象存入spring容器
属性:
value:用于指定bean的id,当我们不写时,默认值是当前类名且首字母改为小写

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

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

@Controller:表现层
@Service:业务层
@Repository:持久层

4.1.2 用于注入数据

@Autowired
作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
出现位置:成员变量上或者方法上
在使用注解注入时,set方法不是必须的
@Qualifier
作用:在按照类型注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用,但是在给方法参数注入时可以。
属性:value:用于指定注入bean的id
在对类成员注入时必须与Autowired一起

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

@Resource
作用:直接按照bean的id注入,可以独立使用。
属性:name:用于指定bean的id

以上三个类型的注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过xml实现

@Value
作用:用于注入基本类型以及String类型的数据
属性:value:用于指定数据的值

4.1.3 用于改变作用范围

@Scope
作用:用于指定bean的作用范围
属性:
value:指定范围的取值。常用取值:singleton,prototype

4.2 基于XML配置文件案例

环境配置

    <!--配置Service-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <!--注入dao-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <!--配置Dao对象-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!--注入QueryRunner-->
        <property name="runner" ref="runner"></property>
    </bean>
    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="datasource"></constructor-arg>
    </bean>
    <!--配置数据源-->
    <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306:springdb"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

测试

/**
 * 使用junit进行单元测试
 */
public class AccountServiceTest {
    
    
    private ApplicationContext ac = null;
    private IAccountService as = null;
    @Before
    public void init() {
    
    
        //获取容器
        ac = new ClassPathXmlApplicationContext("bean.xml");
        //得到业务层对象
        as = ac.getBean("accountService",IAccountService.class);
    }
    @Test
    public void testFindAll() {
    
    
        //执行方法
        List<Account> accounts = as.findAllAccount();
        for (Account account : accounts) {
    
    
            System.out.println(account);
        }
    }

    @Test
    public void testFindOne() {
    
    
        Account account = as.findAccountById(1);
        System.out.println(account);
    }

    @Test
    public void testSave() {
    
    
        Account account = new Account();
        account.setName("Kobe");
        account.setMoney(2400f);
        as.saveAccount(account);
    }

    @Test
    public void testUpdate() {
    
    
        Account account = as.findAccountById(4);
        account.setMoney(8000f);
        as.updateAccount(account);
    }

    @Test
    public void testDelete() {
    
    
        as.deleteAccount(4);
    }
}

4.3 基于注解配置文件案例

配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--告知spring在创建容器时要扫描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>
    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springdb"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>

业务层

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    
    

    @Autowired
    private IAccountDao accountDao;

    @Override
    public List<Account> findAllAccount() {
    
    
        return accountDao.findAllAccount();
    }

    @Override
    public Account findAccountById(Integer accountId) {
    
    
        return accountDao.findAccountById(accountId);
    }

    @Override
    public void saveAccount(Account account) {
    
    
        accountDao.saveAccount(account);
    }

    @Override
    public void deleteAccount(Integer accountId) {
    
    
        accountDao.deleteAccount(accountId);
    }

    @Override
    public void updateAccount(Account account) {
    
    
        accountDao.updateAccount(account);
    }
}
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    
    
    @Autowired
    private QueryRunner runner;

    @Override
    public List<Account> findAllAccount() {
    
    
        try {
    
    
            return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findAccountById(Integer accountId) {
    
    
        try {
    
    
            return runner.query("select * from account where id = ?",new BeanHandler<Account>(Account.class),accountId);
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }

    @Override
    public void saveAccount(Account account) {
    
    
        try {
    
    
            runner.update("insert into account(name,money) values(?,?) ",account.getName(),account.getMoney());
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteAccount(Integer accountId) {
    
    
        try {
    
    
            runner.update("delete from account where id = ?",accountId);
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateAccount(Account account) {
    
    
        try {
    
    
            runner.update("update account set name = ?,money = ? where id = ?",account.getName(),account.getMoney(),account.getId());
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }
}

4.3.1 配置类

@Configuration
作用:指定当前类是一个配置类
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
@ComponentScan
作用:用于通过注解指定spring在创建容器时要扫描的包
属性:value:它和basePackages的作用一样,都是用于指定创建容器时要扫描的包。使用此注解就等同于在xml中
<context:component-scan base-package="com.itheima"></context:component-scan>
@Bean
作用:用于把当前方法的返回值作为Bean对象存入spring的IOC容器中
属性:name:用于指定bean的id,当不写时,默认值是当前方法的名称
细节:当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。查找的方式和Autowired注解的作用一样

/**
 * 配置类
 */
@Configuration
@ComponentScan(basePackages = "com.itheima")
public class SpringConfiguration {
    
    
    /**
     * 用于创建一个QueryRunner对象
     * @param dataSource
     * @return
     */
    @Bean(name = "runner")
    public QueryRunner createQueryRunner(DataSource dataSource){
    
    
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name = "dataSource")
    @Scope("prototype")
    public DataSource createDataSource(){
    
    
        try {
    
    
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/springdb");
            ds.setUser("root");
            ds.setPassword("123456");
            return ds;
        }catch (Exception e){
    
    
            throw new RuntimeException(e);
        }
    }
}

测试类

    private ApplicationContext ac = null;
    private IAccountService as = null;
    @Before
    public void init() {
    
    
        //获取容器
        ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //得到业务层对象
        as = ac.getBean("accountService",IAccountService.class);
    }
    @Test
    public void testFindAll() {
    
    
        //执行方法
        List<Account> accounts = as.findAllAccount();
        for (Account account : accounts) {
    
    
            System.out.println(account);
        }
    }

@Import
作用:用于导入其他的配置类
属性:value:用于指定其他配置类字节码。当我们使用Import注解后,有Import注解的类就是主配置类,导入的都是子配置类

4.3.2 properties文件配置

@PropertySource
作用:用于指定properties文件的位置
属性:value:指定文件的名称和路径
关键字:classpath,表示类路径下

public class JdbcConfig {
    
    
    @Value("${jdbc.driver}")
    private String driver;

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

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

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

    /**
     * 用于创建一个QueryRunner对象
     * @param dataSource
     * @return
     */
    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource){
    
    
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource(){
    
    
        try {
    
    
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
    
    
            throw new RuntimeException(e);
        }
    }
}

4.3.3 Spring整合junit的配置

第一步:导入Spring整合junit的坐标
第二步:使用junit提供的一个注解把原有的main方法替换成spring提供的。@Runwith
第三步:告知Spring的运行器,spring和ioc创建是基于注解还是xml,并说明位置。
@ContextConfiguration
location:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在位置

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
    
    
    @Autowired
    private IAccountService as = null;

    @Test
    public void testFindAll() {
    
    
        //执行方法
        List<Account> accounts = as.findAllAccount();
        for (Account account : accounts) {
    
    
            System.out.println(account);
        }
    }

    @Test
    public void testFindOne() {
    
    
        Account account = as.findAccountById(1);
        System.out.println(account);
    }

    @Test
    public void testSave() {
    
    
        Account account = new Account();
        account.setName("Kobe");
        account.setMoney(2400f);
        as.saveAccount(account);
    }

    @Test
    public void testUpdate() {
    
    
        Account account = as.findAccountById(4);
        account.setMoney(8000f);
        as.updateAccount(account);
    }

    @Test
    public void testDelete() {
    
    
        as.deleteAccount(4);
    }
}

5. AOP相关概念

5.1 AOP概述

5.1.1 基本概念

AOP:Aspect Oriented Programming,面向切面编程。简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

5.1.2 作用及优势

作用:在程序运行期间,不修改源码对已有方法进行增强
优势:减少重复代码,提高开发效率,维护方便
实现方式:动态代理技术

5.2 案例

5.2.1 基本实现

连接工具类

/**
 * 连接的工具类,
 * 它用于从数据源获取一个连接,并且实现和线程的绑定
 */
public class ConnectionUtils {
    
    
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
    
    
        this.dataSource = dataSource;
    }

    /**
     * 获取当前线程上打连接
     * @return
     */
    public Connection getThreadConnection(){
    
    
        //先从ThreadLocal上获取
        Connection conn = tl.get();
        try{
    
    
            //判断当前线程是否有连接
            if (conn == null) {
    
    
                //从数据源中获取一个连接,并且存入ThreadLocal中
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            //实现返回当前线程上的连接
            return conn;
        }catch (Exception e){
    
    
            throw new RuntimeException();
        }
    }

    /**
     *把连接和线程解绑
     */
    public void removeConnection(){
    
    
       tl.remove();
    }
}

事务类

/**
 * 和事务管理工具类
 * 它包含了开启事务,提交事务,回滚事务,释放连接
 */
public class TransactionManager {
    
    

    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
    
    
        this.connectionUtils = connectionUtils;
    }

    /**
     * 开启事务
     */
    public void beginTransaction(){
    
    
        try {
    
    
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public void commit(){
    
    
        try {
    
    
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public void rollback(){
    
    
        try {
    
    
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
    }

    /**
     * 释放连接
     */
    public void release(){
    
    
        try {
    
    
            connectionUtils.getThreadConnection().close();
            connectionUtils.removeConnection();
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
    }
}

Service

/**
 * 账户业务层实现类
 */
public class AccountServiceImpl implements IAccountService {
    
    

    private IAccountDao accountDao;
    private TransactionManager tm;

    public void setTm(TransactionManager tm) {
    
    
        this.tm = tm;
    }

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


    @Override
    public List<Account> findAllAccount() {
    
    
        try{
    
    
            //开启事务
            tm.beginTransaction();
            //执行操作
            List<Account> accounts = accountDao.findAllAccount();
            //提交业务
            tm.commit();
            //返回结果
            return accounts;
        }catch (Exception e){
    
    
            //回滚操作
            tm.rollback();
            throw new RuntimeException(e);
        }finally {
    
    
            //释放连接
            tm.release();
        }
    }

    @Override
    public Account findAccountById(Integer accountId) {
    
    
        try{
    
    
            //开启事务
            tm.beginTransaction();
            //执行操作
            Account account = accountDao.findAccountById(accountId);
            //提交业务
            tm.commit();
            //返回结果
            return account;
        }catch (Exception e){
    
    
            //回滚操作
            tm.rollback();
            throw new RuntimeException(e);
        }finally {
    
    
            //释放连接
            tm.release();
        }
    }

    @Override
    public void saveAccount(Account account) {
    
    
        try{
    
    
            //开启事务
            tm.beginTransaction();
            //执行操作
            accountDao.saveAccount(account);
            //提交业务
            tm.commit();
            //返回结果
        }catch (Exception e){
    
    
            //回滚操作
            tm.rollback();
        }finally {
    
    
            //释放连接
            tm.release();
        }
    }

    @Override
    public void deleteAccount(Integer accountId) {
    
    
        try{
    
    
            //开启事务
            tm.beginTransaction();
            //执行操作
            accountDao.deleteAccount(accountId);
            //提交业务
            tm.commit();
        }catch (Exception e){
    
    
            //回滚操作
            tm.rollback();
        }finally {
    
    
            //释放连接
            tm.release();
        }
    }

    @Override
    public void updateAccount(Account account) {
    
    
        try{
    
    
            //开启事务
            tm.beginTransaction();
            //执行操作
            accountDao.updateAccount(account);
            //提交业务
            tm.commit();
        }catch (Exception e){
    
    
            //回滚操作
            tm.rollback();
        }finally {
    
    
            //释放连接
            tm.release();
        }
    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
    
    
        try{
    
    
            //开启事务
            tm.beginTransaction();
            //执行操作
            //根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //转出账户减钱
            source.setMoney(source.getMoney() - money);
            //转入账户加钱
            target.setMoney(target.getMoney() + money);
            int i = 1/0;
            //更新账户
            accountDao.updateAccount(source);
            accountDao.updateAccount(target);
            //提交业务
            tm.commit();

        }catch (Exception e){
    
    
            //回滚操作
            tm.rollback();
            e.printStackTrace();
        }finally {
    
    
            //释放连接
            tm.release();
        }
    }
}

5.2.2 动态代理

特点
字节码随用随创建,随用随加载
作用
不修改源码的基础上对方法增强
分类
基于接口的动态代理
基于子类的动态代理

5.2.2.1 基于接口的动态代理

创建代理对象
Proxy类中的newProxyInstance方法
创建代理对象的要求
被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance方法参数
ClassLoader:用于加载代理对象字节码。和被代理对象使用相同的类加载器
Class[]:字节码数组,用于让代理对象与被代理对象具有相同的方法
InvocationHandler:用于提供增强的代码,让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类但是不必须,此接口的实现类都是谁用谁写

/**
 * 模拟消费者
 */
public class Client {
    
    
    public static void main(String[] args) {
    
    
        final Producer producer = new Producer();
        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
    
    
                    /**
                     * 作用:执行被代理对象的任何接口方法都会经过该方法
                     * @param proxy:代理对象的引用
                     * @param method:当前执行的方法
                     * @param args:当前执行方法所需要的参数
                     * @return:和被代理对象有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
                        //增强代码
                        Object returnValue = null;
                        //获取方法执行的参数
                        Float money = (Float)args[0];
                        //判断当前方法是否为销售
                        if ("saleProduct".equals(method.getName())){
    
    
                            returnValue = method.invoke(producer, money * 0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(1000f);
    }
}
5.2.2.2 基于子类的动态代理

涉及的类
Enhancer
创建代理对象
使用Enhancer类中的create方法
创建代理对象的要求
被代理类不能是最终类
create方法的参数
Class:字节码。用于指定被代理对象的字节码
Callback:用于提供增强的代码,让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类但是不必须,此接口的实现类都是谁用谁写。我们一般都写的是该接口的子接口实现类:MethodIntercept

public class Client {
    
    
    public static void main(String[] args) {
    
    
        final Producer producer = new Producer();
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
    
    
            /**
             * 执行该代理对象的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             * 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
             * @param methodProxy:当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    
    
                //增强代码
                Object returnValue = null;
                //获取方法执行的参数
                Float money = (Float) args[0];
                //判断当前方法是否为销售
                if ("saleProduct".equals(method.getName())){
    
    
                    returnValue = method.invoke(producer, money * 0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(8000f);
    }
}
5.2.2.3 案例改进

创建Service代理对象

public class BeanFactory {
    
    
    private IAccountService accountService;

    private TransactionManager txManager;

    public void setTxManager(TransactionManager txManager) {
    
    
        this.txManager = txManager;
    }

    public final void setAccountService(IAccountService accountService) {
    
    
        this.accountService = accountService;
    }

    /**
     * 获取Service代理对象
     * @return
     */
    public IAccountService getAccountService() {
    
    
        return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
    
    
                    /**
                     * 添加事务的支持
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
                        Object rtValue = null;
                        try {
    
    
                            //开启事务
                            txManager.beginTransaction();
                            //执行操作
                            rtValue = method.invoke(accountService, args);
                            //提交事务
                            txManager.commit();
                            //返回结果
                            return txManager;
                        }catch (Exception e) {
    
    
                            txManager.rollback();
                            throw new RuntimeException(e);
                        }finally {
    
    
                            //释放连接
                            txManager.release();
                        }
                    }
                });
    }
}

5.3 Spring中的AOP

5.3.1 Spring中基于XML的AOP配置步骤

1.把通知Bean也交给spring来管理
2.使用aop:config标签表明开始AOP的配置
3.使用aop:aspect标签表明开始配置切面
属性:id:给切面提供一个唯一标志
ref:指定通知类bean的id
4.在aopAspect标签内部使用对应的标签来配置通知的类型
aop:before表示前置通知
属性:method:用于指定哪个方法是前置通知
pointcut:用于指定切入点表达式,该表达式的含义是指对业务层中哪些方法增强

5.3.1.1 切入点表达式写法

关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名…类名.方法名(参数列表)

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置spring的IOC,把service对象配置进来-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
    <!--spring中基于xml的AOP配置步骤-->
    <!--配置Logger类-->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>
    <!--开始AOP配置-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的类型,并且建立通知方法和切入点方法的关系-->
            <aop:before method="printLog" pointcut="execution(public void com.itheima.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

全通配写法:

<aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>

返回值可以使用通配符,表示任意返回值

<aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.saveAccount())"></aop:before>

包名可以使用…表示当前包集启子包

<aop:before method="printLog" pointcut="execution(* *..AccountServiceImpl.saveAccount())"></aop:before>

包名可以使用通配符,表示任意包。但是有几级包就需要写几个*

            <aop:before method="printLog" pointcut="execution(* *.*.*.*.AccountServiceImpl.saveAccount())"></aop:before>

参数列表:
可以直接写数据类型,基本类型直接写名称,引用类型写包名.类名的方式

            <aop:before method="printLog" pointcut="execution(* *..*.*(int))"></aop:before>

类型可以使用通配符表示任意类型,但是必须有参数

            <aop:before method="printLog" pointcut="execution(* *..*.*(*))"></aop:before>

可以使用…表示有无参数均可,有参数可以是任意类型

            <aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>

业务层通用写法(实际开发使用)

            <aop:before method="printLog" pointcut="execution(com.itheima.service.impl.*.*(..))"></aop:before>
5.3.1.2 四种通知类型
    <!--开始AOP配置-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置前置通知,执行于切入点方法之前-->
            <aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>

            <!--配置后置通知,执行于切入点方法正常执行之后-->
            <aop:after method="afterPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after>

            <!--配置异常通知,执行于切入点方法产生异常之后-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-throwing>

            <!--配置最终通知,无论切入点方法是否正常执行,都会在其后面执行-->
            <aop:after-returning method="afterRturningPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-returning>

        </aop:aspect>
    </aop:config>

配置切入点表达式
此标签智能写在aop:aspect标签内部只能当前切面使用。它还可以写在aop:aspect外面,所有切面可以用

    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置前置通知,执行于切入点方法之前-->
            <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>

            <!--配置后置通知,执行于切入点方法正常执行之后-->
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>

            <!--配置异常通知,执行于切入点方法产生异常之后-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>

            <!--配置最终通知,无论切入点方法是否正常执行,都会在其后面执行-->
            <aop:after-returning method="afterRturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
            <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        </aop:aspect>
    </aop:config>

环绕通知

    <aop:config>
        <!--配置切面-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        <aop:aspect id="logAdvice" ref="logger">
        <!--配置环绕通知-->
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>

问题:当配置了环绕通知以后,切入点方法没有执行,而通知方法执行了
分析:通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法使用,而这里的代码中没有
解决:Spring框架为我们提供了一个接口ProceedingJoinPoint,该接口proceed()方法就相当于明确调用切入点方法,该接口可以作为环绕通知的方法参数,在程序执行时,Spring框架为我们提供该接口的实现类

    public Object aroundPrintLog(ProceedingJoinPoint pjp){
    
    
        Object rtValue = null;
        try {
    
    
            Object[] args = pjp.getArgs();
            System.out.println("Logger类中环绕通知被执行");  //前置通知
            pjp.proceed(args);  //明确调用业务层方法
            System.out.println("Logger类中环绕通知被执行");  //后置通知
            return rtValue;
        } catch (Throwable throwable) {
    
    
            System.out.println("Logger类中环绕通知被执行");  //异常通知
            throw new RuntimeException(throwable);
        } finally {
    
    
            System.out.println("Logger类中环绕通知被执行");  //最终通知
        }
    }

环绕通知是Spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式

5.3.2 Spring中基于注解的AOP配置步骤

    <!--配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>
    <!--配置spring开启注释AOP的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@Component("logger")
@Aspect //当前类是一个切面类
public class Logger {
    
    

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){
    
    }

    /**
     * 前置通知
     */
    @Before("pt1()")
    public void beforePrintLog(){
    
    
        System.out.println("Logger类中前置通知被执行");
    }

    /**
     * 后置通知
     */
    @AfterReturning("pt1()")
    public void afterPrintLog(){
    
    
        System.out.println("Logger类中后置通知被执行");
    }

    /**
     * 异常通知
     */
    @AfterThrowing("pt1()")
    public void afterThrowingPrintLog(){
    
    
        System.out.println("Logger类中异常通知被执行");
    }

    /**
     * 最终通知
     */
    @After("pt1()")
    public void afterRturningPrintLog(){
    
    
        System.out.println("Logger类中最终通知被执行");
    }

    @Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
    
    
        Object rtValue = null;
        try {
    
    
            Object[] args = pjp.getArgs();
            System.out.println("Logger类中环绕通知被执行");  //前置通知
            pjp.proceed(args);  //明确调用业务层方法
            System.out.println("Logger类中环绕通知被执行");  //后置通知
            return rtValue;
        } catch (Throwable throwable) {
    
    
            System.out.println("Logger类中环绕通知被执行");  //异常通知
            throw new RuntimeException(throwable);
        } finally {
    
    
            System.out.println("Logger类中环绕通知被执行");  //最终通知
        }
    }
}

关于出现0 can’t find referenced pointcut,@Pointcut() 部分重写

@Component("logger")
@Aspect //当前类是一个切面类
public class Logger {
    
    

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){
    
    }

    /**
     * 前置通知
     */
    @Before("execution(* com.itheima.service.impl.*.*(..))")
    public void beforePrintLog(){
    
    
        System.out.println("Logger类中前置通知被执行");
    }

    /**
     * 后置通知
     */
    @AfterReturning("execution(* com.itheima.service.impl.*.*(..))")
    public void afterPrintLog(){
    
    
        System.out.println("Logger类中后置通知被执行");
    }

    /**
     * 异常通知
     */
    @AfterThrowing("execution(* com.itheima.service.impl.*.*(..))")
    public void afterThrowingPrintLog(){
    
    
        System.out.println("Logger类中异常通知被执行");
    }

    /**
     * 最终通知
     */
    @After("execution(* com.itheima.service.impl.*.*(..))")
    public void afterRturningPrintLog(){
    
    
        System.out.println("Logger类中最终通知被执行");
    }

    //@Around("execution(* com.itheima.service.impl.*.*(..))")
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
    
    
        Object rtValue = null;
        try {
    
    
            Object[] args = pjp.getArgs();
            System.out.println("Logger类中环绕通知被执行");  //前置通知
            pjp.proceed(args);  //明确调用业务层方法
            System.out.println("Logger类中环绕通知被执行");  //后置通知
            return rtValue;
        } catch (Throwable throwable) {
    
    
            System.out.println("Logger类中环绕通知被执行");  //异常通知
            throw new RuntimeException(throwable);
        } finally {
    
    
            System.out.println("Logger类中环绕通知被执行");  //最终通知
        }
    }
}

6. Spring中的JdbcTemplate

作用:用于和数据库交互,实现对表的CRUD操作

/**
 * JdbcTemplate基本用法
 */
public class JdbcTemplateDemo1 {
    
    
    public static void main(String[] args) {
    
    
        //准备数据源:spring内置数据源
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost3306/springdb");
        ds.setUsername("root");
        ds.setPassword("123456");
        //创建JdbcTemplate对象
        JdbcTemplate jt = new JdbcTemplate();
        //给jt设置数据源
        jt.setDataSource(ds);
        //执行操作
        jt.execute("insert into account(name,money) values(ccc,1000);");
    }
}

6.1 JdbcTemplate在Spring的IOC中使用

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

    <!--配置数据源-->
    <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/springdb"></property>
        <property name="username" value="root"></property>
        <property name="password" value="lzh199824"></property>
    </bean>
public class JdbcTemplateDemo2 {
    
    
    public static void main(String[] args) {
    
    
        //获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //获取对象
        JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
        //执行操作
        jt.execute("insert into account(name,money) values('ddd',1000)");
    }
}

6.1.1 JdbcTemplate在的CRUD操作

/**
 * JdbcTemplate的CRUD操作
 */
public class JdbcTemplateDemo3 {
    
    
    public static void main(String[] args) {
    
    
        //获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //获取对象
        JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
        //执行操作
        //保存
//        jt.update("insert into account(name,money) values(?,?)","Kobe",2400f);
        //更新
//        jt.update("update account set name=?,money=? where id=6","Kobe",8000f);
        //删除
//        jt.update("delete from account where id=?",6);
        //查询所有
//        List<Account> accounts = jt.query("select * from account where money > ?", new AccountRowMapper(), 800f);
        //spring实现封装
//        List<Account> accounts = jt.query("select * from account where money > ?", new BeanPropertyRowMapper<Account>(Account.class), 800f);
//        for (Account account : accounts) {
    
    
//            System.out.println(account);
//        }
        //查询一个
        List<Account> accounts = jt.query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), 1);
        System.out.println(accounts.isEmpty()?"用户不存在":accounts.get(0));
        //查询返回一行一列,使用聚合函数,但不加group by子句
        Integer num = jt.queryForObject("select count(*) from account where money > ?", Integer.class, 800f);
        System.out.println(num);
    }
}

/**
 * 定义Account的封装策略
 */
//class AccountRowMapper implements RowMapper<Account>{
    
    
//    /**
//     * 把结果集中的数据封装到Account中,然后由Spring把每个Account加到集合中
//     * @param rs
//     * @param rowNum
//     * @return
//     * @throws SQLException
//     */
//    @Override
//    public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
    
    
//        Account account = new Account();
//        account.setId(rs.getInt("id"));
//        account.setName(rs.getString("name"));
//        account.setMoney(rs.getFloat("money"));
//        return account;
//    }
//}

6.1.2 JdbcTemplate在Dao中使用

/**
 * 账户持久层实现类
 */
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
    
    


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

    @Override
    public Account findByName(String accountName) {
    
    
        List<Account> accounts = 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);
    }

    @Override
    public void updateAccount(Account account) {
    
    
        getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
    }
}

    <!--配置账户的持久层-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
/**
 * 用于抽取Dao中的重复代码
 */
public class JdbcDaoSupport {
    
    
    private JdbcTemplate jdbcTemplate;

    private DataSource dataSource;

    public DataSource getDataSource() {
    
    
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
    
    
        this.dataSource = dataSource;
        if (jdbcTemplate == null){
    
    
            jdbcTemplate = createJdbcTemplate(dataSource);
        }
    }

    private JdbcTemplate createJdbcTemplate(DataSource dataSource) {
    
    
        return new JdbcTemplate(dataSource);
    }

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

    public JdbcTemplate getJdbcTemplate() {
    
    
        return jdbcTemplate;
    }
}

6.1.3 基于XML的AOP实现事务控制

配置文件

    <!--配置Service-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <!--注入dao-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <!--配置Dao对象-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!--注入QueryRunner-->
        <property name="runner" ref="runner"></property>
        <!--注入connectionUtils-->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
    <!--配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springdb"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!--配置Connection工具类-->
    <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务管理器-->
    <bean id="txManager" class="com.itheima.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--配置aop-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        <aop:aspect id="txAdvice" ref="txManager">
            <!--配置前置通知:开启事务-->
            <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
            <!--配置后置通知:提交事务-->
            <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
            <!--配置异常通知:回滚事务-->
            <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
            <!--配置最终通知:释放连接-->
            <aop:after method="release" pointcut-ref="pt1"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

6.1.4 基于注解的AOP实现事务控制

配置文件

    <context:annotation-config/>

    <!--配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
    <!--配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springdb"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!--开启spring对注解AOP的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@Component("txManager")
@Aspect
public class TransactionManager {
    
    

    @Autowired
    private ConnectionUtils connectionUtils;

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){
    
    }

    /**
     * 开启事务
     */
    @Before("execution(* com.itheima.service.impl.*.*(..))")
    public void beginTransaction(){
    
    
        try {
    
    
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    @AfterReturning("execution(* com.itheima.service.impl.*.*(..))")
    public void commit(){
    
    
        try {
    
    
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    @AfterThrowing("execution(* com.itheima.service.impl.*.*(..))")
    public void rollback(){
    
    
        try {
    
    
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
    }

    /**
     * 释放连接
     */
    @After("execution(* com.itheima.service.impl.*.*(..))")
    public void release(){
    
    
        try {
    
    
            connectionUtils.getThreadConnection().close();
            connectionUtils.removeConnection();
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
    }

    @Around("execution(* com.itheima.service.impl.*.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint pjp){
    
    
        Object rtValue = null;
        try {
    
    
            //获取参数
            Object[] args = pjp.getArgs();
            //开启事务
            this.beginTransaction();
            //执行方法
            rtValue = pjp.proceed(args);
            //提交事务
            this.commit();
            return rtValue;
        }catch (Throwable throwable){
    
    
            //回滚事务
            this.rollback();
            throw new RuntimeException(throwable);
        }finally {
    
    
            //释放资源
            this.release();
        }
    }
}

6.2 Spring中的事务控制

6.2.1 基于xml的声明事务控制

步骤
1.配置事务管理器
2.配置事务的通知
此时我们需要导入事务的约束,tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务起一个唯一标志
transaction-manager:给事务通知起一个事务管理器引用
3.配置 aop中的通用切入点表达式
4.建立事务通知和切入点表达式的对应关系
5.配置事务的属性,在事务的通知tx:advice标签内部
isolation:用于指定事务的隔离级别,默认级别时DEFAULT,表示使用数据库的默认隔离级别
no-rollback-for:用于指定一个异常,当指定该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,表示任何异常都回滚
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务增删改的选择
read-only:用于指定事务是否只读。只有查询方法才能设定为true。默认值是false,表示读写。
rollback-for:用于指定一个异常,当指定该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定数值,以秒为单位

    <!--spring中基于xml的声明事务控制配置步骤-->
    <!--配置事务管理器-->
    <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="*" propagation="REQUIRED" read-only="false"></tx:method>
            <tx:method name="find" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>

    <!--配置aop-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        <!--建立切入点表达式和事务通知的对应关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>

6.2.2 基于注解的声明事务控制

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

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

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

    <!--配置数据源-->
    <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/springdb"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!--开启spring对注解事务的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

6.2.3 基于纯注解的声明事务控制

/**
 * 和事务相关配置类
 */
public class TransactionConfig {
    
    

    /**
     * 用于创建事务管理器对象
     * @param dataSource
     * @return
     */
    @Bean(name="transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource){
    
    
        return new DataSourceTransactionManager(dataSource);
    }
}
/**
 * 连接数据库相关配置类
 */
public class JdbcConfig {
    
    

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

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

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

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

    /**
     * 创建JdbcTemplate对象
     * @param dataSource
     * @return
     */
    @Bean(name="jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
    
    
        return new JdbcTemplate(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name="dataSource")
    public DataSource createDataSource(){
    
    
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}
/**
 * spring的配置类,相当于bean.xml
 */
@Configuration
@ComponentScan("com.itheima")
@Import({
    
    JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
    
    
}

猜你喜欢

转载自blog.csdn.net/qq_44708714/article/details/111411912