Spring第二天

Spring02

回顾

ioc入门
	控制反转:将bean的创建和销毁权交给spring管理.降低耦合
	导入依赖:
		spring-context 和 junit
	编写配置文件:
		名称:自定义 建议applicationContext.xml
		位置:建议 resources目录下
		导入beans的约束
			<bean id="名字" class="全限定名">
	测试:
		工厂对象
			ApplicationContext context = new ClasspathXmlApplicationContext(配置文件)
			Object obj = context.getBean("bean的名字")
	ApplicationContext和BeanFactory
	ApplicationContext的两个实现类
    	ClasspathXmlApplicationContext
    	AnnotationConfigApplicationContext
bean的配置:
	id
	class
	scope
        常见值:
            singleton:默认
            prototype
    了解:
        init-method
        destroy-method

bean的实例化方法:
    无参构造器(常用)
    工厂静态方法实例化
   	工厂普通方法实例化

DI:依赖注入 ioc的核心
	在spring创建bean的同时,将bean拥有的成员字段设置值进去.
	方式:
		属性set方法
			在bean中给成员变量提供set方法
			bean标签:
				property标签
					name属性:
					value属性:指定基本类型和String类型
					ref属性:指定存在spring容器中的bean类型
		构造器
			bean标签:
				constructor-arg标签
					name属性
					value属性 : 
					ref属性 : 
		p名称空间(底层使用的就是属性set方式)
			导入p名称空间
			<bean id="" class="" p:属性名="" p:属性名-ref=""/>

复杂类型注入(集合)
	数组 list set
		list标签
            value标签
            ref标签
            bean标签
    map properties
		map标签
			entry标签

import:导入其他的spring配置文件.
	<import resource="spring-di.xml"/>

DBUtils:jdbc工具类
	使用步骤:
        1. 导入jar依赖(dbutils,mysql驱动,druid,test,spring-context)
        2. 创建核心对象 new QueryRunner(数据源)
        3. 调用方法
           - update :
           - query :
           		ResultSetHandler:接口
           			BeanHandler
           			BeanListHandler
           			ScalarHandler
spring和DBUtils整合
	找到单实例对象,把他们交给spring管理

抽取jdbc配置信息
	<context:property-placeholder location="classpath:xxx.properties"/>

内容介绍

  1. ioc的注解方式
  2. spring整合junit
  3. 完成转账案例(spring+dbutils)

一 Spring中IOC注解开发

1 常用注解

Spring常用注解主要是替代<bean> 的配置

<bean id="名字" class="类的全限定类名" scope="" init-method="" destory-method="">
	<property name="属性名" value|ref=""/>
</bean>

spring中ioc的注解要想使用,需要先开启组件扫描(扫描指定包及其子包下的所有带有四个指定注解的类,创建好对象,放入spring容器中)

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

使用在类上的注解

@Component:组件
  • 替代bean标签,将类的产生的对象加入到spring容器中
  • value属性:设置在spring容器中的名字.也可以不写名字,名字的默认值就是类名首字母小写
  • 在开发中经常使用的时候它的三个衍生的注解(子注解)
@Controller: 作用在web层类上
@Service: 作用service层类上
@Repository: 作用dao层类上

三个衍生的注解目前和Component一样,几乎没有区别,但是建议根据类的位置使用不同的注解

//@Component(value = "userDao")//交给spring容器管理,且命名为userDao
@Component//交给spring管理,使用的默认名字(就是类名,首字母小写)
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("userDaoImpl中的save方法执行了");
    }
}
@Scope:作用域

了解.一般开发的时候不加,使用的就是它的默认值:单实例

使用value属性配置单实例还是多实例

使用在方法上的注解(了解)

@PostConstruct:替代init-method属性

@PreDestroy:替代destory-method属性

使用在成员变量上的注解-注入(掌握)

可以不给成员变量提供set方法.也可以提供set方法

注解可以作用在成员变量上,也可以作用在set方法上.

@Value
  • 给简单类型的成员变量进行赋值
@Autowired
  • 给引用类型的成员变量进行赋值.值是已经存在于spring容器中,相当于ref属性
  • 默认是按照数据类型注入.
    • 若只找到一个此类型的对象,直接注入
    • 若找到了多个此类型的对象,把变量名作为名字去spring容器中匹配.若匹配到了直接注入;若匹配不到就会报异常
    • 可以搭配使用==@Qualifier==注解,使用它的value属性指定名字进行注入(装配)

推荐只使用==@Autowired==,前提就要求只在spring容器中存放一个指定的类型的对象.

@Resource

  • jdk提供的注解做的事情和@Autowired及@Qualifier的作用是一样的

小结

目前在配置文件中要开启组件扫描

  • @Component,替代bean标签
    • @Controller
    • @Service
    • @Respository
  • @Scope
  • @Value
  • @Autowired
    • 搭配@Qualifier,一般不用

2 使用spring常用注解整合DBUtils

需求:

​ 完成对账户表的查询和删除操作

步骤分析:

  1. 环境搭建:
    • 新建一个模块:spring02_2_dbutils_anno_xml
    • 导入依赖:spring-context mysql驱动 druid dbutils junit
    • 复制db.properties
    • 复制Account
    • 新建AccountDao及其实现类,AccountService及其实现类
  2. 编写一个beans.xml
    • 加载db.properties
    • 开启组件扫描
    • 配置数据源
    • 配置QueryRunner(注入数据源)
  3. 修改AccountDaoImpl
    • 在类上添加@Repositroy,加入spring容器中
    • 在成员变量上添加@Autowired
  4. 修改AccountServiceImpl
    • 在类上添加@Service,加入spring容器中
    • 在成员变量上添加@Autowired
  5. 测试

代码实现:

pom.xml

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

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

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

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.9</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

beans.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: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">
    <!--加载properties文件-->
    <context:property-placeholder location="classpath:db.properties"/>

    <!--配置组件扫描-->
    <context:component-scan base-package="com.itheima"/>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!--注入四个基本属性-->
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--配置queryRunner-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>
</beans>

AccountDao

@Repository //交给spring容器
public class AccountDaoImpl implements AccountDao {

    @Autowired //注入QueryRunner
    private QueryRunner qr;

    @Override
    public void delete(int id) throws SQLException {
        qr.update("delete from account where id = ?",id);
    }

    @Override
    public Account findById(int id) throws SQLException {
        return qr.query("select * from account where id = ?",new BeanHandler<>(Account.class),id);
    }
}

AccountService

@Service //交给spring容器管理
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public void delete(int id) throws SQLException {
        accountDao.delete(id);
    }

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

小结:

将我们自己编写的类使用ioc和di注解替代.但是QueryRunner和DruidDatasource两个类不是我们编写的,是第三方编写,没有办法在他们上面添加注解,目前我们只能将他们还配置在xml文件.

3 使用spring纯注解整合DBUtils

将beans.xml干掉,使用注解替换掉

spring新注解(在spring boot中用)

@Configuration:声明当前类是一个spring的配置类 == beans.xml

@Bean:作用在方法上,将方法的返回值(对象)交给容器管理 针对的是非自己编写的类对应的对象

@PropertySource:加载properties配置文件

@ComponentScan:组件扫描

@Import :导入其他注解类

使用spring的纯注解步骤分析:

  1. 复制spring02_2_dbutils_anno_xml模块,重命名为spring02_3_dbutils_anno
  2. 编写一个配置类,名字自定义. 例如:SpringConfig
    • 类上使用@PropertySource加载properties文件
    • 类上使用@ComponentScan 扫描组件
    • 类上使用@Configuration 声明是一个配置类,目前可以不写
    • 在类中编写俩方法,一个返回值为数据源,另一个返回值为queryRunner.使用@Bean注解将返回值加入spring管理即可
  3. 测试
    • 使用配置类创建spring容器对象

代码实现:

@Configuration //声明是一个配置类,目前可以不写
//@PropertySource(value = {"classpath:db.properties"})//加载properties配置文件即可
//@PropertySource({"classpath:db.properties"})//加载properties配置文件即可
@PropertySource("classpath:db.properties")//加载properties配置文件即可
@ComponentScan("com.itheima")//配置扫描的包
public class SpringConfig {

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


    //@Bean //将方法的返回值加入spring容器管理,名字使用的是默认名字(方法名)
    @Bean("ds") //将方法的返回值加入spring容器管理,名字使用的是ds
    public DataSource createDS(){
        DruidDataSource ds = new DruidDataSource();

        //设置基本属性
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }

    @Bean
    public QueryRunner createQR(DataSource dataSource){//会自动的注入
        return new QueryRunner(dataSource);
    }
}

获得spring容器API就是

//使用注解配置类 创建spring容器对象
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

注解模块化

  • 并集配置 (了解)
 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class, DaoConfig.class);

  • 主从配置

    通过@Import注解引入其他的配置类

    //@Configuration //目前可加可不加,推荐加
    public class DaoConfig {
        @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 //将方法的返回值加入spring容器管理,名字使用的是默认名字(方法名)
        @Bean("ds") //将方法的返回值加入spring容器管理,名字使用的是ds
        public DataSource createDS(){
            DruidDataSource ds = new DruidDataSource();
    
            //设置基本属性
            ds.setDriverClassName(driver);
            ds.setUrl(url);
            ds.setUsername(username);
            ds.setPassword(password);
            return ds;
        }
    
        @Bean
        public QueryRunner createQR(DataSource dataSource){//会自动的注入
            return new QueryRunner(dataSource);
        }
    }
    
    

二 Spring整合Junit

spring中有一个类继承了junit中类,对类中运行的方法进行增强操作,让代码运行的时候知道有spring容器的存在,且可以从容器中拿到要注入的对象.

1 步骤分析

  1. 导入依赖
    • 额外导入:spring-test
  2. 在测试类上添加两个注解
    • 指定spring配置文件位置或者spring配置类,底层会创建spring容器
    • 指定测试类使用spring测试类,因为这个测试类知道spring容器的存在
  3. 在测试类中注入需要测试的对象
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>

注意:

  1. spring-test版本要和spring-context版本一致
  2. junit的版本在4.12(含)之上

2 xml配置

@ContextConfiguration("classpath:beans.xml") //告诉运行类,spring配置文件或者配置类在哪
@RunWith(SpringJUnit4ClassRunner.class) //指定运行的时候使用spring增强的测试类
public class TestSpringJunit {

    @Autowired
    AccountService accountService;

    @Test
    public void testFindById() throws SQLException {
        Account account = accountService.findById(2);
        System.out.println(account);
    }

    @Test
    public void testDelete() throws SQLException {
        accountService.delete(2);
    }
}

3 注解

@ContextConfiguration(classes = {SpringConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class TestSpringJunit {

    @Autowired
    AccountService accountService;

    @Test
    public void testFindById() throws SQLException {
        Account account = accountService.findById(2);
        System.out.println(account);
    }
}

三 转账案例-不考虑事务

1 需求

​ 使用spring框架整合DBUtils技术,实现用户转账功能

2 业务分析:

​ service层中有一个方法 transfer(String fromUser,String toUser,int money)

​ dao层中有两个方法:accountOut(减钱)和accountIn(加钱)

3 步骤分析

  1. 新建一个模块,spring02_4_account_dbutils(不考虑事务)
    • 导入依赖:spring-context,mysql驱动,druid,dbutils,junit和spring-test
    • 复制db.properties
  2. 新建accountDao及其实现类
    • 将dao交给spring管理
    • 添加queryRunner成员变量,且注入
    • 编写accountOut和accountIn方法
  3. 新建accountService及其实现类
    • 将service交给spring管理
    • 添加accountDao成员变量,且注入
    • 编写transfer方法
  4. 编写配置文件
    • 加载properties文件
    • 开启组件扫描
    • 配置数据源
    • 配置queryRunner
  5. 测试

四 转账案例-考虑事务

1 上述案例存在的问题

转账过程中,若出现了异常,有可能总金额会变少,不符合事务的一致性原则.原因:在dao中的两个操作使用的不同的连接对象,推出来它们使用的两个事务.mysql中的事务默认是自动提交的,一句sql就是一个事务.

2 解决方案

将service层中的transfer方法中的所有操作放入同一个事务中.我们只需要保证service中使用的连接对象和dao中使用的连接对象是同一个即可.

开启事务: conn.setAutoCommit(false)

提交事务: conn.commit()

回滚事务: conn.rollback()

方案1:在service层先获取一个连接对象,开启事务,将此连接对象传递给dao中方法,务必保证dao中的操作使用的是刚才传递过来的连接对象.不太优雅不推荐

方案2:使用ThreadLocal解决.让我们的当前线程和使用的connection对象绑定在一起. service和dao中一次操作都处在同一个线程中.

可以使用threadlocal将一些数据绑定到当前线程中,只要使用的是同一个线程的话,获取到线程上绑定的数据.

3 技术分析

ThreadLocal

参考TheadLocal.md文件

4 代码实现

ConnectionManager

//连接管理类
@Component
public class ConnectionManager {
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();

    @Autowired
    private DataSource dataSource;

    //* getThreadConnection//获取当前线程上绑定的连接
    //	先获去当前线程上绑定的连接,若有直接用.若无则从连接池中获取一个连接,然后绑定到当前线程上
    public Connection getThreadConnection() throws SQLException {
        // 先获去当前线程上绑定的连接,若有直接用.
        Connection conn = tl.get();

        // 若无则从连接池中获取一个连接,然后绑定到当前线程上
        if (null == conn) {
            conn = dataSource.getConnection();
            tl.set(conn);
        }

        return conn;
    }


    //* release//将连接和当前线程解绑,归还连接
    public void release() throws SQLException {
        Connection conn = getThreadConnection();

        //解绑连接
        tl.remove();

        //归还连接
        conn.close();
    }
}

TransactionManager:

//事务管理类
@Component //交给spring管理
public class TransactionManager {

    @Autowired
    ConnectionManager connectionManager;

    //* beginTransaction//开启事务
    public void beginTransaction() throws SQLException {
        //获取当前线程上绑定的连接,开启事务
        connectionManager.getThreadConnection().setAutoCommit(false);
    }

    //* commit//提交事务
    public void commit() throws SQLException {
        //	获取当前线程上绑定的连接,提交事务
        connectionManager.getThreadConnection().commit();
    }

    //* rollback//回滚事务
    public void rollback(){
        //	获取当前线程上绑定的连接,回滚事务
        try {
            connectionManager.getThreadConnection().rollback();
        } catch (SQLException e) {
           //静默操作
        }
    }

    //* release//释放资源
    public  void release(){
        //	获取当前线程上绑定的连接,重置连接自动提交事务,调用ConnectionManager中的release解绑连接和归还连接
        try {
            Connection conn = connectionManager.getThreadConnection();
            conn.setAutoCommit(true);

            connectionManager.release();
        } catch (SQLException e) {
           //静默处理
        }
    }
}

AccountService

@Service //交给spring管理
public class AccountServiceImpl implements AccountService {

    @Autowired
    AccountDao accountDao;

    @Autowired
    TransactionManager transactionManager;

    @Override
    public void tranfer(String fromUser, String toUser, int money) throws SQLException {


        try {
            //开启事务
            transactionManager.beginTransaction();

            //减钱的操作
            int i = accountDao.accountOut(fromUser, money);
            System.out.println("减钱成功的人数:"+i);
            if (i!=1){
                throw  new RuntimeException("减钱出现了问题");
            }

            //加钱的操作
            i = accountDao.accountIn(toUser,money);
            System.out.println("加钱成功的人数:"+i);
            if (i!=1){
                throw  new RuntimeException("加钱出现了问题");
            }

            //提交事务
            transactionManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //若有问题,回滚事务
            transactionManager.rollback();

            //通知web层
            throw e;
        } finally {
            //无论如何都需要释放资源
            transactionManager.release();
        }
    }
}

AccountDao

@Repository//交给spring管理
public class AccountDaoImpl implements AccountDao {

    @Autowired
    QueryRunner runner;

    @Autowired
    ConnectionManager connectionManager;

    @Override
    public int accountIn(String toUser, int money) throws SQLException {
        //使用当前线程上绑定的连接去操作数据库
        int i = runner.update(connectionManager.getThreadConnection(),"update account set money = money + ? where name = ?", money, toUser);
        return i;
    }

    @Override
    public int accountOut(String fromUser, int money) throws SQLException {
        //使用当前线程上绑定的连接去操作数据库
        int i = runner.update(connectionManager.getThreadConnection(),"update account set money = money - ? where name = ?", money, fromUser);
        return i;
    }
}

beans.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: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:property-placeholder location="classpath:db.properties"/>

    <!--开启组件扫描-->
    <context:component-scan base-package="com.itheima"/>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--配置queryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>

</beans>

注意:

  • 在dao中务必使用从当前线程上获取的连接进行操作;若没有传入连接的话,使用的从连接池上获取的新连接,这样的话就不能保证使用的同一个事务了

测试类

@ContextConfiguration("classpath:beans.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestTransfer {

    @Autowired
    AccountService accountService;

    @Test
    public void testTransfer() throws SQLException {
        accountService.tranfer("tom","jackson",100);
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值