2021-03-03-spring-01

一、Spring概述

1.1 Spring概述

1.1.1 什么是spring?
  spring是分层的javaSE/EE应用 全栈 轻量级开源框架,以IOC为核心和Aop为内核,提供了展现层MVC和持久层SpringJDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多的第三方框架和类库。
1.1.2 代码展示如何解耦?
常规调用:业务层层调用持久层(dao层=new dao层实现类)
     表现层调用业务层(service层= new service层实现类)
dao层代码:

public interface UserDao {
    void saveUser();
}

dao层实现类:
/**

  • 用户的持久层实现类
    */
public class UserDaoImpl implements UserDao {

    @Override
    public void saveUser() {
        System.out.println("保存账户");
    }
}

service层代码:
/**

  • 业务层接口
  • 管理用户
    */
public interface UserService {

    /**
     * 模拟保存
     */
    void saveUser();
}

service层实现类:

public class UserServiceImpl implements UserService {

    private UserDao userDao  = new UserDaoImpl();

    //private UserDao userDao = (UserDao) BeanFactory.getBean("userDao");


    /**
     *业务层调用持久层
     */
    @Override
    public void saveUser() {
        userDao.saveUser();
    }
}

主方法类:

/**
 * 表现层调用业务层
 */
public class Client {

    public static void main(String[] args) {
        for (int i = 1; i < 3; i++) {
            UserService userService = new UserServiceImpl();
            //UserService userService = (UserService) BeanFactory.getBean("userService");
            System.out.println(userService);
            userService.saveUser();
        }
    }
}

通过创建外部properties配置文件,存入key以及对应的实现类的完全限定名。通过工厂模式,将各层之间的调用方式改变。通过bean工厂生产对象,从而实现解耦。中间利用了反射来创建工厂对象,将生产出来的对象放在容器中,从而避免反复创建对象带来的资源消耗。调用对象的模式也从多例模式转变为单例模式。
实例代码如下:
首先是BeanFactory工厂类:

/**
 * 一个创建bean对象的工厂
 *
 * Bean在计算机中,有可重用组件的含义
 * JavaBean != 实体类 用java语言编写的可重用组件
 *
 * 特就是创建我们的service和Dao对象
 *
 * 第一个:需要一个配置文件,来配置service和dao
 *        配置的内容,唯一标识 = 全限定类名(key = value)
 * 第二个:通过读取配置文件的配置的内容,反射创建对象
 */
public class BeanFactory {
    //创建一个静态的properties对象
    private static Properties p;

    //定义一个Map用与存放我们要创建对象,称之为容器
    private static Map<String ,Object> beans;
    //创建一个静态代码块用来获取bean.Properties配置文件
    static {
        try {
            //new 一个Properties对象
            p = new Properties();
            //利用类加载器获取bean.properties的流对象
            InputStream ip = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            //使用properties对象的load方法载入流对象
            p.load(ip);
            //实例化容器
            beans = new HashMap<String,Object>();
            //利用properties的keys方法获取所有的key
            Enumeration keys = p.keys();
            //遍历枚举
            while (keys.hasMoreElements()){
                //取出每个key
                String key = keys.nextElement().toString();
                //根据key获取value
                String beanPath = p.getProperty(key);
                //反射创建实例对象
                Object value = Class.forName(beanPath).newInstance();
                //把key和value存入beans容器中
                beans.put(key,value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据bean的key获取对象value
     *
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        return beans.get(beanName);
    }

    /**
     * newInstance
     * public T newInstance()
     *               throws InstantiationException,
     *                      IllegalAccessException创建此 Class 对象所表示的类的一个新实例。如同用一个带有一个空参数列表的 new 表达式实例化该类。如果该类尚未初始化,则初始化这个类。
     * 注意,此方法传播 null 构造方法所抛出的任何异常,包括已检查的异常。使用此方法可以有效地绕过编译时的异常检查,而在其他情况下编译器都会执行该检查。 Constructor.newInstance 方法将该构造方法所抛出的任何异常包装在一个(已检查的)InvocationTargetException 中,从而避免了这一问题。
     *
     *
     * 返回:
     * 此对象所表示的类的一个新分配的实例。
     * 抛出:
     * IllegalAccessException - 如果该类或其 null 构造方法是不可访问的。
     * InstantiationException - 如果此 Class 表示一个抽象类、接口、数组类、基本类型或 void; 或者该类没有 null 构造方法; 或者由于其他某种原因导致实例化失败。
     * ExceptionInInitializerError - 如果该方法引发的初始化失败。
     * SecurityException - 如果存在安全管理器 s,并满足下列任一条件:
     * 调用 s.checkMemberAccess(this, Member.PUBLIC) 拒绝创建该类的新实例
     * 调用者的类加载器不同于也不是当前类的类加载器的一个祖先,并且对 s.checkPackageAccess() 的调用拒绝访问该类的包
     * @param beanName
     * @return
     */
    /*//定义一个方法,从流对象中获取配置文件对应完全限定类的路径,思路,利用反射的思想创建bean
    //利用配置文件,将对象变为bean存储在工厂中
    public static Object getBean(String beanName){
        //创建bean对象
        Object bean = null;
        //调用properties对象的getProperty方法传入参数beanName获取响应的key,也就是bean.properties配置文件的key
        String beanPath = p.getProperty(beanName);
        try {
            //通过反射,创建该路径下的类对象实例
            bean = Class.forName(beanPath).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return bean;
    }*/
}

然后是更改之后的业务层,跟表现层主方法类:

public class UserServiceImpl implements UserService {

    //private UserDao userDao  = new UserDaoImpl();

    private UserDao userDao = (UserDao) BeanFactory.getBean("userDao");


    /**
     *业务层调用持久层
     */
    @Override
    public void saveUser() {
        userDao.saveUser();
    }
}
/**
 * 表现层调用业务层
 */
public class Client {

    public static void main(String[] args) {
        for (int i = 1; i < 3; i++) {
            //UserService userService = new UserServiceImpl();
            UserService userService = (UserService) BeanFactory.getBean("userService");
            System.out.println(userService);
            userService.saveUser();
        }
    }
}

1.1.3 IOC
控制反转,将原本由程序员控制的创建对象的权利交由工厂处理,削减了计算机的耦合,大大降低了程序各个对象之间的依赖。

二、使用spring的IOC解决程序的耦合问题

1.1 使用spring的ioc来解决程序间的依赖关系

使用DI实现依赖注入,bean通过bean注入的步骤是:

  1. 创建spring的xml配置文件,将对象通过配置文件的方式交由spring容器进行管理。
  2. 通过bean标签中的属性对注入的bean对象进行管理,示例如下:
<bean id="userService" scope="prototype" init-method="init" destroy-method="destroy" class="com.itheima.service.impl.UserServiceImpl"></bean>

其中id标签代表自定义的bean组件的名称,scope便签可以选择单例模式或者多例模式即singleton和prototype。init-method表示bean组件通过spring创建之后执行的方法,destroy-method表示容器销毁之后执行的方法。
自我理解bean的执行周期,在单例模式下:1)容器创建时立马创建对象 ,2)初始化对象,3)执行对象中的方法或者不执行, 4)销毁容器,对象死亡
在多例模式下:1)使用对象的时候spring帮我们创建对象 ,2)对象只要在使用过程中就会一直存活,3)多例模式不会自动销毁容器,当对象长时间不使用,垃圾回收期会回收对象或者手动关闭容器,对象此时就是死亡的状态。

  1. bean的作用域
      作用域的作用,用于指定bean对象的作用范围:
      singleton:单例模式
      prototype:多例模式
      request:作用域web应用的请求域
      session:作用域web应用的会话范围
      globalSession:作用于集群中多个服务器的session范围
  2. bean对象创建的三种方式:
    1. 构造函数创建
        在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
    //使用构造函数创建
    public  UserServiceImpl() {
    }

XML配置:

<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean>
  1. 使用某个类的方法创建
  //使用InstanceFactory类的方法创建bean对象
public class InstanceFactory {
    private UserService getUserService(){
        return new UserServiceImpl();
    }
}
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
    <bean id="userService" factory-bean="instanceFactory" factory-method="getUserService"></bean>
  1. 使用静态类的静态方法创建对象
//使用静态方法创建bean
public  class StaticFactory {
    private static UserService getUserService(){
        return new UserServiceImpl();
    }
}
<bean id="userService" class="com.itheima.factory.StaticFactory" factory-method="getUserService"></bean>
  1. spring的依赖注入DI
    作用:降低耦合,也就是对象之间的互相调用
    能够注入的三种数据类型:String的基本类型、bean类型、集合
    注入的方式:
     1. 构造函数注入–不推荐使用
    标签出现的位置:bean标签的内部
    标签的属性:
    type:用于指定要注入的数据的数据类型,改数据类型也是构造函数中某个或者某些参数的类型
    index:用于指定注入的数据给构造函数中指定索引位置的参数赋值,索引的位置从0开始
    name:用于指定给构造函数中指定名称的参数赋值
    以上三个用于指定给构造函数的那个参数赋值==
    value:用于提供基本类型String类型的数据
    ref:用于指定其他的bean类型数据,要求在spring的ioc容器中出现的bean对象
    优势:
    在获取bean对象时,注入数据是必须的操作
    弊端:
    改变了bean对象的实例化方式,使我们想使用这些对象时,如果不用这些数据也必须提供
    示例代码如下:
//构造函数注入
public UserServiceImpl(String name, Integer age, Date time){
    this.name = name;
    this.age = age;
    this.time = time;
}
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
    <constructor-arg index="0" value="张三"/>
    <constructor-arg name="age" value="81"/>
    <constructor-arg name="time" ref="now"/>
</bean>
<bean id="now" class="java.util.Date"></bean>

第二种setter方法注入:

public class UserServiceImpl2 implements UserService {
    private String name;
    private Integer age;
    private Date time;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setTime(Date time) {
        this.time = time;
    }
<!--set方法注入=====比较常用
        标签:property
        位置:bean标签内部
        标签的属性:
           name:用于指定注入时所调用的set方法名称
           value:用于提供基本类型和String类型的数据
           ref:指定其他的bean类型数据
        优势:
           创建对象时,没有明确的限制,可以直接使用默认构造函数
        弊端:
           如果某个成员必须有值,获取对象时有可能set方法没有执行
    -->
    <bean id="userService1" class="com.itheima.service.impl.UserServiceImpl2">
        <property name="name" value="22"></property>
        <!--像这样,容器无法发现set有没有执行
        <property name="age" value="20"></property>-->
        <property name="time" ref="now"></property>
    </bean>

特殊种类集合类型的注入:

<!--集合类型的注入
        用于给List结构集合注入的标签有: list set array
        用于给Map结构集合注入的标签有: map props
        结构一样标签可以互换
    -->
    <bean id="userService3" class="com.itheima.service.impl.UserServiceImpl3">
        <property name="myt">
          <array>
              <value>1</value>
              <value>2</value>
              <value>3</value>
              <value>4</value>
          </array>
        </property>
        <property name="lists">
            <list>
                <value>a</value>
                <value>b</value>
                <value>c</value>
            </list>
        </property>
        <property name="maps">
            <map>
                <entry key="s" value="ss"></entry>
                <entry key="b" value="bb"></entry>
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="ssss">bbbb</prop>
            </props>
        </property>

1.2 常用的IOC注解,按照作用分类。

曾经的XML的配置:

<bean id="accountService" scope="singleton" init-method="findAll" destroy-method="findAll" class="com.itheima.service.impl.AccountServiceImpl">
        <!--注入dao对象-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>
  • 用于创建对象的:作用和XML配置文件中编写一个标签实现的功能是一样的。@Component注解,用于将当前类存入spring容器中,属性key,value:用于在核定bean的id,默认bean的id为类名称小写。要想加了注解的类可以被扫描为bean,有两种配置方式。使用配置文件配置bean时,1)在spring的bean.xml文件中加入context头文件和对应的标签指定spring创建对象时需要扫描的包。如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

<!--用于告知spring创建容器时需要扫描的包,配置所需的标签不是在beans的约束中,而是一个
名称为context名称空间和约束中-->
<context:component-scan base-package="com.itheima"></context:component-scan>
</beans>

2)使用注解配置时,需要在配置类上加上@CompoentScan注解,value为需要扫描的包的类路径。
3)@Component注解的衍生注解,有@Controller:一般用于表现层;@Service 一般用于业务层 ;@Repository一般用于持久层,以上三个注解和component的作用是一样的,使三层对象更清晰

@ComponentScan("com.itheima")//这个注解加载配置类类名上方
  • 用于注入数据的:作用和XML配置文件中的bean标签里面的标签的作用是一样的。
    注解:@Autowired作用:自动按照类型注入,只要容器中有唯一的bean对象类型和要注入的变量类型匹配就可有注入成功。出现位置,可以是成员变量上也可以是方法上。使用set注入不需要set方法。可以搭配@Qualifier一起使用,在按照类型注入的基础上再按照名称进行进一步筛选。还有一个注解@Resource可以直接按照bean的名称注入,不用依靠@Autowired,他的属性是name。@Value注解:用于注入基本类型和String类型的数据,用于指定数据的值,可以使用SPEL表达式
@Autowired()//按照类型注入
@Qualifier("accountDao")//用于指定bean的名称注入,搭配@注解Autowired,按照名称注入
@Resource(description = "accountDao")//直接按照bean的名称注入,可以单独使用
@Value("${jdbc.driver}")//SPEL表达式
private String driver;
@Value("2")//基本类型数据注入
private int x;
  • 用于改变作用范围的:作用和bean标签中的标签一样
    @Scope改变bean的作用范围,默认是Singleton
  • 和生命周期有关的:init-method destroy-method作用一样
    @PreDestory 用于指定销毁方法@PostConstruct用于指定初始化方法

1.3 IOC案例

  1. 准备数据表
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `money` float NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES (1, '嘿嘿嘿', 10000);
INSERT INTO `account` VALUES (3, 'ccc', 1000);
INSERT INTO `account` VALUES (4, '仨', 1000);
  1. pom文件
<?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.itheima</groupId>
    <artifactId>anli</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
    </dependencies>

</project>
  1. 持久层,业务层,配置文件代码(没有注释,请谅解)
    实体类,省去get ,set ,toString方法
public class Account {
    private Integer id;
    private String  name;
    private Float money;
    }

持久层以及持久层实现类:

public interface AccountDao {
    List<Account> findAll();

    Account findById(Integer id);

    void save(Account account);

    void deleteById(Integer id);

    void updateAccount(Account account);
}

持久层实现类,使用dbUtil操作数据库

public class AccountDaoImpl implements AccountDao {

    //创建QueryRunner对象
    private QueryRunner runner;

    //创建set方法没使用set方法注入bean
    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

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

    @Override
    public Account findById(Integer id) {
        try {
            return runner.query("select *from account where id = ?",new BeanHandler<Account>(Account.class),id);
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }
    @Override
    public void save(Account account) {
        try {
            runner.update("insert into account(name,money)values(?,?) ",account.getName(),account.getMoney());
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }

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

    @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();
        }
    }
}

业务层以及业务员层实现类:

public interface AccountService {

    List<Account> findAll();

    Account findById(Integer id);

    void save(Account account);

    void deleteById(Integer id);

    void updateAccount(Account account);
}
public class AccountServiceImpl implements AccountService {

    //使用set方法注入bean
    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }

    @Override
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

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

    @Override
    public void deleteById(Integer id) {
        accountDao.deleteById(id);
    }

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

最最重要的spring核心配置文件:

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

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <!--注入dao对象-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <property name="runner" ref="runner"></property>
    </bean>
    <!--每次使用对象都创建新的对象,所以选择bean作用范围为多例-->
    <bean id="runner" scope="prototype" class="org.apache.commons.dbutils.QueryRunner">
        <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/db3?characterEncoding=UTF-8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
     </bean>
</beans>

抽取出来的公共模块,用来加载配置文件,获取bean:

public class BeanUtil {
    public static AccountService getBean(){

        ApplicationContext as = new ClassPathXmlApplicationContext("bean.xml");
        AccountService accountService = as.getBean("accountService", AccountService.class);
        return accountService;
    }
}

案例的另外一种写法,使用注解,抛弃配置文件。

数据表,pom文件同上:
数据库配置文件:jdbcConfig.xml

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db3?characterEncoding=UTF-8
jdbc.user=root
jdbc.password=root

持久层实现类的一些改变:

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    //通过bean的类型注入
    @Autowired
    private QueryRunner runner;

业务层实现类的改变:注解注入,不用set方法

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

    @Autowired()//按照类型注入
    //@Qualifier("accountDao")//用于指定bean的名称注入,搭配@注解Autowired,按照名称注入
    //@Resource(description = "accountDao")//直接按照bean的名称注入,可以单独使用
    private AccountDao accountDao;

springConfig配置类

@Configuration//声明这是一个配置类
public class SpringConfig {

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

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

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

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

    //通过调用构造函数实例化,将返回的对象作为bean加入容器,并命名为runner
    @Bean(name="runner")
    @Scope("prototype")
    //@Qualifier可以直接指定响应的参数
    public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource dataSource) {
        return new QueryRunner(dataSource);
    }
    @Bean(name="dataSource")
    public DataSource createDataSource() {
        try {
            ComboPooledDataSource c = new ComboPooledDataSource();
            c.setDriverClass(driver);
            c.setJdbcUrl(url);
            c.setUser(username);
            c.setPassword(password);
            return c;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

使用注解的加载容器的方式:

public class BeanUtil {
    public static AccountService getBean(){

        ApplicationContext as = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        AccountService accountService = as.getBean("accountService", AccountService.class);
        return accountService;
    }
}

主配置类:

@ComponentScan("com.itheima")//表示创建容器时需要扫描的包
@Import(SpringConfig.class)//将子配置类导入总配置类
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}

@ComponentScan用于指定创建容器时需要扫描的包
@Import 用于导入其他的配置类,有Import注解的父配置类,导入的式子配置类。可以同时导入多个子配置类。
@PropertyResource 用于指定properties文件的名称和位置,关键字:classpath表示类路径下。

@Qualifier//通过参数名称直接指定参数
    public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource dataSource) {}

使用spring-test spring整合junit单元测试类完成测试,去除冗余的重复代码,并且可以解耦

//表示替换默认测试main方法
@RunWith(SpringJUnit4ClassRunner.class)
/**表示使用哪个配置文件或者配置类作为启动类
*  locations:指定xml文件的位置,加上classpath关键字表示在类路径下
   classes:指定注解类所在位置
*/
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountTest {

    @Autowired
    private AccountService as;

    @Test
    public void findAll(){
        //获取全部用户信息

        List<Account> all = as.findAll();
        for (Account account : all) {
            System.out.println(account);
        }
    }
    @Test
    public void findOne(){
        Account byId = as.findById(1);
        System.out.println(byId);
    }
    @Test
    public void save(){
        Account account = new Account();
        account.setName("仨");
        account.setMoney(1000f);
        as.save(account);
    }
    @Test
    public void delete(){
        as.deleteById(2);
    }
    @Test
    public void update(){
        Account account = new Account();
        account.setId(1);
        account.setName("嘿嘿嘿");
        account.setMoney(10000f);
        as.updateAccount(account);
    }
}

三、AOP

1.1 动态代理

1.代理模式的概念
代理模式是23中设计模式中的一种,是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B旺旺实现了一个看接口,A也会去实现接口。但是B是真正的实现类,那么A借用了B的方式去实现接口的方法。A可以通过增强B,在调用B的方法前后做些其他的事情。如果例子中代理A写死了B,那么A就是B的静态代理对象,如果A代理的对象时不确定的,那么就是动态代理,B是被代理对象,B是一个接口的实现类。
动态代理中常见的实现是,JDK动态代理和cglib动态代理。
2. 使用jdk的方式实现动态代理
接口代码:

   public interface Proxy1 {
    /**
     * 销售产品
     * @param money
     */
     void sale(int money);
}

实现类:

/**
 * 生产者
 */
public class Produce {
    /**
     * 销售产品
     * @param money
     */
    public void sale(int money) {
        System.out.println("销售产品"+money);
    }
}
/**
 * 模拟消费者
 */
public class Client {
    public static void main(String[] args) {
        final Produce produce = new Produce();//注此处要用final修饰
        /**
         * 动态代理:
         * 特点:字节码随用随创建,随用随加载
         * 作用:不修改源码的基础上对方法增强
         * 分类:
         *     基于接口的动态代理
         *     基于子类的动态代理
         * 基于接口的动态代理:
         *     设计的类proxy
         *     提供者:JDK官方
         * 如何创建代理对象:
         *     使用proxy类中的newProxyInstance方法
         * 创建代理对象的要求:
         *     被代理对象至少使用一个接口
         * newProxyInstance接口的方法参数:
         *     ClassLoader:类加载器
         *        用于加载代理对象字节码的,和被代理对象使用相同的类加载器。固定写法
         *     Class[]:字节码数组
         *        用于让代理对象和被代理对象具有相同方法
         *     InvocationHandler:用于提供增强的代码
         *        让我们写如何代理,一般是一些该接口的实现类,通常情况下都是匿名实现类,但不是必须的
         *
         *
         */
        //创建代理对象代理对象的类型为代理接口
        Proxy1 proxy1 = (Proxy1) Proxy.newProxyInstance(
                produce.getClass().getClassLoader(),
                produce.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 returnVale = null;
                        //获取方法执行的参数
                        int money = (int)args[0];
                        //判断当前方法是不是销售
                        if ("sale".equals(method.getName())){
                            returnVale = method.invoke(produce,(int)(money*0.9));
                        }
                        return returnVale;
                    }
                });
        proxy1.sale(10000);
    }
}

基于cglib的动态代理:
加入cglib依赖:

  <dependency>
         <groupId>cglib</groupId>
         <artifactId>cglib</artifactId>
         <version>2.1_3</version>
     </dependency>
/**
 * 生产者
 */
public class Produce{
    /**
     * 销售产品
     * @param money
     */
    public void sale(int money) {
        System.out.println("销售产品"+money);
    }
    /**
     * 售后
     */
    public void afterSale(int money){
        System.out.println("售后:" + money);
    }
}

使用cglib完成动态代理:

/**
        * 模拟消费者
        */
public class Client {
    public static void main(String[] args) {
        final Produce produce = new Produce();

        /**
         * 动态代理:
         * 特点:字节码随用随创建,随用随加载
         * 作用:不修改源码的基础上对方法增强
         * 分类:
         *     基于接口的动态代理
         *     基于子类的动态代理
         * 基于子类的动态代理:
         *     涉及的类enhancer
         *     提供者:第三方cglib库
         * 如何创建代理对象:
         *     使用Enhance类中的create方法
         * 创建代理对象的要求:
         *     被代理对象不能是最终类
         * create的方法参数:
         *     Class字节码:
         *        用于指定被代理对象的字节码
         *
         *     callBack:用于提供增强的代码
         *        让我们写如何代理,一般是一些该接口的实现类,通常情况下都是匿名实现类,但不是必须的
         *        一般使用该接口的子接口实现类:MethodInterceptor
         *
         *
         */
        Produce p = (Produce) Enhancer.create(produce.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             * 以上三个参数和基于接口的动态代理对象的参数是一样的
             * @param methodProxy:当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //创建增强的代码
                Object returnValue = null;
                //获取方法执行的参数
                int money = (int)args[0];

                //判断当前方法是不是销售
                if ("sale".equals(method.getName())){
                    returnValue = method.invoke(produce,(int)(money*0.8));
                }
                return returnValue;
            }
        });
     p.sale(1000);
    }
}

1.2 spring基于XML和注解的AOP配置

实现代码:
接口

/**
 * 账户的业务接口
 */
public interface AccountService {
    /**
     * 模拟保存账户
     */
    void saveAccount();
    /**
     * 模拟更新账户
     */
    void updateAccount(int i);
    /**
     * 模拟删除账户
     */
    int deleteAccount();
}

实现类

public class AccountServiceImpl implements AccountService {
    @Override
    public void saveAccount() {
        System.out.println("执行保存");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("执行更新");
    }

    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

日志配置

/**
 * 用于记录日志的工具类,里面提供了公共的代码
 */
public class Logger {
    /**
     * 用于打印日志,计划让其在切入点方法之前执行(切入点是业务层方法)
     */
    public void printLog(){
        System.out.println("Logger开始记录日志");
    }
}

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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置spring的ioc,把service对象配置进去-->
    <bean id="accountService" class="com.itheima.serivice.impl.AccountServiceImpl"></bean>


    <!--spring中基于XML的AOP的配置步骤
      1.把通知bean交给spring管理
      2.使用aopConfig标签表名开始AOP的配置
      3.使用aop:aspect标签表名配置的切面:
          id属性:为切面提供唯一标识
          ref属性:指定通知类bean的id
      4.在aop:aspect标签的内部使用对应的标签来配置通知的类型
          我们现在的示例是在切入点方法执行之前调用日志类的printLog方法,所以是前置通知aop:before
          method属性:用于指定Logger类中那哪个方法是前置通知
          pointcut属性用于指定对业务中哪些方法增强

          切入点表达式的写法:
               关键字:execution(表达式)
               表达式:
                   访问修饰符  返回值  包名,包名。。。类名.方法名.参数别表
               标准的表达式写法:
                   public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
               访问修饰符可以省略
                   void com.itheima.service.impl.AccountServiceImpl.saveAccount()
               返回值可以使用通配符*,表示任意值
                   * com.itheima.service.impl.AccountServiceImpl.saveAccount()
               包名使用通配符,表示任意包,有几级包就是用几个*
                   * *.*.*.*.AccountServiceImpl.saveAccount()
               包名可以使用..表示当前包或者子包
                   * *..AccountServiceImpl.saveAccount()
               类名和方法名都可以使用*来实现统配
                   * *..*.*()
               使用类型的通配符
               * *..*.*(int)
               全通配写法:
                   * *..*.*(..)
               实际开发中切入点表达式的通常方法:
                   一般是切入到业务层实现类下的所有方法:
                      * com.itheima.serivice.impl.*.*(..)
      -->
    <!--配置logger类-->
    <bean id="logger" class="com.itheima.serivice.util.Logger"></bean>

    <!--配置aop-->
    <aop:config>
        <aop:aspect id="logAdvice" ref="logger">
            <aop:before method="printLog" pointcut="execution(* com.itheima.serivice.impl.*.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

测试方法

/**
 * 测试aop配置
 */
public class AopTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        AccountService accountService = (AccountService) applicationContext.getBean("accountService");
        accountService.saveAccount();
        accountService.updateAccount(1);
        accountService.deleteAccount();
    }
}

返回结果

Logger开始记录日志
执行保存
Logger开始记录日志
执行更新
Logger开始记录日志
执行了删除

1.3 AOP的使用

1. 基于XML配置文件文件的使用:

实体类

/**
 * 账户的实体类
 */
public class Account implements Serializable {

    private Integer id;
    private String name;
    private Float money;
    }

持久层实现类:

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

    private QueryRunner runner;
    private ConnectionUtils connectionUtils;

    //set方式注入
    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    //set方式注入
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    @Override
    public List<Account> findAllAccount() {
        try{
            return runner.query(connectionUtils.getThreadConnection(),"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(connectionUtils.getThreadConnection(),"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(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

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

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

    @Override
    public Account findAccountByName(String accountName) {
        try{
            List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
            if(accounts == null || accounts.size() == 0){
                return null;
            }
            if(accounts.size() > 1){
                throw new RuntimeException("结果集不唯一,数据有问题");
            }
            return accounts.get(0);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

util工具类:
连接数据库工具

/**
 * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
 */
public class ConnectionUtils {

    //获取当前线程上的连接
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    private DataSource dataSource;

    //set方式注入DataSource数据源
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

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

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

事务管理工具类:


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

    private ConnectionUtils connectionUtils;

    //set方式注入连接模板对象
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * 开启事务
     */
    public  void beginTransaction(){
        try {
            System.out.println("start");
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public  void commit(){
        try {
            System.out.println("提交事务");
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public  void rollback(){
        try {
            System.out.println("回滚事务");
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    /**
     * 释放连接
     */
    public  void release(){
        try {
            System.out.println("释放连接");
            connectionUtils.getThreadConnection().close();//还回连接池中
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

     <!-- 配置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/db3?characterEncoding=UTF-8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!-- 配置Connection的工具类 ConnectionUtils -->
    <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
        <!-- 注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置事务管理器-->
    <bean id="txManager" class="com.itheima.utils.TransactionManager">
        <!-- 注入ConnectionUtils -->
        <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>

测试类:

/**
 * 使用Junit单元测试:测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private  IAccountService as;

    @Test
    public  void testTransfer(){
        as.transfer("嘿嘿嘿","ccc",100f);
    }
}

pom文件配置:

<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.itheima</groupId>
    <artifactId>acount</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <!--spring IOC坐标-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--spring整合单元测试坐标-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--数据库控制工具QueryRunner-->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>
        <!--mysql连接工具-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!--数据源-->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <!--单元测试类-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--AOP-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
</project>

基于注解的AOP使用:
实体类一致:
持久层实现类于类上方加入@Repository(“beanId”),注入的bean上方使用@Autowired注解,根据类型注入,去除set注入方法。
修改部分代码如下
持久层实现类:

/**
 * 账户的持久层实现类
 */
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {

    //注解自动注入
    @Autowired
    private QueryRunner runner;
 
    //注解自动注入连接工具类对象
    @Autowired
    private ConnectionUtils connectionUtils;

业务层实现类:

/**
 * 账户的业务层实现类
 *
 * 事务控制应该都是在业务层
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService{

    //注解自动注入持久层类对象
    @Autowired
    private IAccountDao accountDao;

工具类:

/**
 * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
 */
//将连接工具类作为bean加入spring容器
@Component("connectionUtils")
public class ConnectionUtils {

    //新建ThreadLocal对象
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    //注入数据源对象
    @Autowired
    private DataSource dataSource;
/**
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
@Component("txManager")
@Aspect
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;
    //声明切点的路径
    //针对impl包下的包含任意参数的任意类的任意方法
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){
    }
    /**
     * 开启事务
     * 方法执行前,调用
     */
    @Before("pt1()")
    public  void beginTransaction(){
        try {
            System.out.println("start");
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 提交事务
     * 方法执行后调用
     */
    @AfterReturning("pt1()")
    public  void commit(){
        try {
            System.out.println("提交事务");
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 回滚事务
     */
    @AfterThrowing("pt1()")
    public  void rollback(){
        try {
            System.out.println("回滚事务");
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 释放连接
     */
    @After("pt1()")
    public  void release(){
        try {
            System.out.println("释放连接");
            connectionUtils.getThreadConnection().close();//还回连接池中
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

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

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <!--配置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/db3?characterEncoding=UTF-8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

</beans>

测试类:

/**
 * 使用Junit单元测试:测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private  IAccountService as;

    @Test
    public  void testTransfer(){
        as.transfer("嘿嘿嘿","ccc",100f);
    }
}

四、JdbcTemplate入门

1.1 JdbcTemplate简单使用

示例代码:

/**
 * 账户实体类
 */
public class Account {
    private Integer id;

    private String name;

    private Float money;
}

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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

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

    <!--配置spring内置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///db3?characterEncoding=UTF-8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
</beans>

测试:

public class JdbcTemplateDemo1 {

    public static void main(String[] args) {
        //获取容器
        ApplicationContext as = new ClassPathXmlApplicationContext("bean.xml");
        //获取bean
        JdbcTemplate jb = (JdbcTemplate) as.getBean("jdbcTemplate");
        //jb.execute("insert into account(name,money)values('ccc',1000) ");
        //List<Account> accounts = jb.query("select * from account where money = ?", new AccountRowMapper(), 1000);

        /**
         * 正常开发中,通常使用BeanPropertyRowMapper<>(Xxx.class)
         */
        List<Account> accounts = jb.query("select * from account where money = ?", new BeanPropertyRowMapper<>(Account.class), 1000);
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
}

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

1.2 jdbcTemplate在Dao中的使用

省略实体类
持久层接口代码:

/**
 * jdbcTemplate在dao层的使用
 */
public interface AccountDao {

    /**
     * 通过id查询
     * @param id
     * @return
     */
    Account findById(Integer id);
}

持久层实现类代码:

/**
 * 账户持久层实现类
 */
public class AccountDaoImpl implements AccountDao {
    //创建jdbcTemplate对象
    private JdbcTemplate jdbcTemplate;
    //使用set方式注入
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

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

测试:

/**
 * 账户持久层实现类
 */
public class JdbcTemplateDemo2 {
    public static void main(String[] args) {
        ApplicationContext as = new ClassPathXmlApplicationContext("bean.xml");
        AccountDao accountDao = (AccountDao)as.getBean("accountDao");
        Account byId = accountDao.findById(1);
        System.out.println(byId);
    }
}

1.3 jdbcDaoSupport的使用

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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置spring内置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///db3?characterEncoding=UTF-8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
</beans>
/**
 * 继承JdbcDaoSupport类
 * 的账户持久层实现类
 */
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {


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

    @Override
    public Account findByName(String name) {
        return null;
    }

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

其他类基本一致,参考上方即可

1.4 基于xml文件方式配置声明式事务控制

实体类同上,持久层业务层采用set方式注入,持久层使用JdbcDaoSupport进行业务支持。核心是通过配置文件的方式实现声明式事务控制,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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
">
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


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

    <!--配置spring内置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///db3?characterEncoding=UTF-8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--配置事务管理器
        配置事务管理器
        配置事务的通知
            1.此时需要导入事务的约束 tx名称空间和约束,同时需要配置aop
            2.使用tx:advice标签配置事务通知
                属性:
                  id:给事务通知起一个唯一标识
                  transaction-manager:给事务通知提供一个事务管理器引用
            3.配置AOP的通用切入点表达式
            4.建立事务通知和切入点的对应关系
            5。配置事务的属性:
                  在事务的tx:advice标签中配置

        -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--配置事务的属性:
                isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的隔离级别
                read-only:用于指定事务是否只读,只有查询方法才能设置为true,默认值为false,表示读写
                timeout:用于指定事务的超时时间,默认值为-1表示永不超时,如果指定了数值以秒为单位
                propagation:用于指定事务的传播行为,默认值为required,表示一定会有事务,增删改的选择,查询方法可以用Supports
                no-rollback-for="":表示产生某个异常不回滚,产生其他异常回滚
                rollback-for="":表示产生某个特定的异常回滚其他异常不回滚
            -->
        <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>
</beans>

1.5 通过注解的方式实现声明式事务控制

与xml配置方式变动的包括,持久层,业务层注入的方式,持久层不能通过继承JdbcDaoSupport实现业务操作。变动代码如下:
dao层

@Repository("accountDao")
public class AccountDaoImpl  implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

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

service层:

@Service("accountService")
@Transactional//开启事务
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

bean.xml文件

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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.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>

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


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

    <!--配置spring内置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///db3?characterEncoding=UTF-8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--spring基于注解的声明式事务控制配置步骤
        1、配置事务管理器
        2、开启spring对注解事务的支持
        3、在需要开启事务的地方加上@Transactional注解
        -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

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

</beans>

在这里插入图片描述](https://img-blog.csdnimg.cn/20210309112655831.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTI4NTU0Mg==,size_16,color_FFFFFF,t_70)

在这里插入图片描述

1.6 纯注解方式实现声明式事务

将数据库信息,jdbcTemplate TransactionManager 通过配置类的形式创建对象,在使用注解注入到spring容器中,持久层,业务层同上,多了几个配置类

jdbc.properties数据库文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///db3?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root

JdbcConfig配置类:

/**
 * 连接数据库相关配置类
 */
@Configuration
@PropertySource("jdbc.properties")//可以放在总配置类也可以放在子配置类
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;


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

   @Bean(name = "dataSource")
    public DataSource getDataSource(){
       DriverManagerDataSource ds = new DriverManagerDataSource();
       ds.setDriverClassName(driver);
       ds.setUrl(url);
       ds.setUsername(username);
       ds.setPassword(password);

       return ds;
   }
}

TransactionManager事务配置类:

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

    /**
     * 用于创建事务管理器你对象
     * @param dataSource
     * @return
     */
    @Bean(name = "transactionManager")
    public PlatformTransactionManager createTrans(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

SpringConfig总配置类:

@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class,TransactionConfig.class})
//@PropertySource("jdbc.properties")

//开启注解支持
@EnableTransactionManagement
public class SpringConfig {
}

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值