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"/>
内容介绍
- ioc的注解方式
- spring整合junit
- 完成转账案例(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
需求:
完成对账户表的查询和删除操作
步骤分析:
- 环境搭建:
- 新建一个模块:spring02_2_dbutils_anno_xml
- 导入依赖:spring-context mysql驱动 druid dbutils junit
- 复制db.properties
- 复制Account
- 新建AccountDao及其实现类,AccountService及其实现类
- 编写一个beans.xml
- 加载db.properties
- 开启组件扫描
- 配置数据源
- 配置QueryRunner(注入数据源)
- 修改AccountDaoImpl
- 在类上添加@Repositroy,加入spring容器中
- 在成员变量上添加@Autowired
- 修改AccountServiceImpl
- 在类上添加@Service,加入spring容器中
- 在成员变量上添加@Autowired
- 测试
代码实现:
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的纯注解步骤分析:
- 复制spring02_2_dbutils_anno_xml模块,重命名为spring02_3_dbutils_anno
- 编写一个配置类,名字自定义. 例如:SpringConfig
- 类上使用@PropertySource加载properties文件
- 类上使用@ComponentScan 扫描组件
- 类上使用@Configuration 声明是一个配置类,目前可以不写
- 在类中编写俩方法,一个返回值为数据源,另一个返回值为queryRunner.使用@Bean注解将返回值加入spring管理即可
- 测试
- 使用配置类创建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 步骤分析
- 导入依赖
- 额外导入:spring-test
- 在测试类上添加两个注解
- 指定spring配置文件位置或者spring配置类,底层会创建spring容器
- 指定测试类使用spring测试类,因为这个测试类知道spring容器的存在
- 在测试类中注入需要测试的对象
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
注意:
- spring-test版本要和spring-context版本一致
- 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 步骤分析
- 新建一个模块,spring02_4_account_dbutils(不考虑事务)
- 导入依赖:spring-context,mysql驱动,druid,dbutils,junit和spring-test
- 复制db.properties
- 新建accountDao及其实现类
- 将dao交给spring管理
- 添加queryRunner成员变量,且注入
- 编写accountOut和accountIn方法
- 新建accountService及其实现类
- 将service交给spring管理
- 添加accountDao成员变量,且注入
- 编写transfer方法
- 编写配置文件
- 加载properties文件
- 开启组件扫描
- 配置数据源
- 配置queryRunner
- 测试
四 转账案例-考虑事务
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);
}
}