Spring day04
1 Spring基于注解的开发
XML方式配置bean存在的问题:开发效率低下。Spring2.x提供了开发效率更高的注解式配置。注解开发替换XML配置的好处:简化编程,提高开发效率。
- XML方式:配置繁琐,但功能强大,维护性好,可配置第3方类型(非自己编码的类型)
- 注解方式:配置简单,开发效率高。一般用于自定义类型的配置
1.1 注解开发的思路
1.2 注解开发的步骤
-
查找注解,配置查找注解的起始包名
applicationContext.xml
<!-- 扫描指定包下以及子包下所有使用注解的类,自动配置为Bean--> <context:component-scan base-package="com.bz"/>
-
为类和属性添加注解
package com.bz.service.impl; import com.bz.entity.User; import com.bz.mapper.UserMapper; import com.bz.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component("userService") public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; public void setUserMapper(UserMapper userMapper) { this.userMapper = userMapper; } @Override public boolean login(String name, String password) { User user = userMapper.selectUserByName(name); return user!= null && user.getPassword().equals(password); } }
1.3 核心注解
1.3.1 bean配置相关注解
@Component替换bean标签,创建对象。
@Controller 用在action层
@Service 用在service
@Repository 用在dao层
@Service
//@Component("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
...
}
注意:
-
Controller Service Repository和Component作用一样,但更有识别性。
-
MyBatis框架下,Repository注解没有必需的使用场景
-
4个注解使用时,都可以不用设置id属性,id默认值:类名首字母小写
例如:UserServiceImpl ==> userServiceImpl
@Scope 决定对象是否单例
默认对象是单例的,可以通过@Scope(“prototype”)设置为多例
1.3.2 属性注入的相关注解
@Autowired 用来完成属性注入。
@Service
public class UserServiceImpl implements UserService {
@Autowired(required = false)
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
...
}
注意:
- Autowired根据属性的类型,从Spring容器中获取对象,完成属性赋值
- Autowired用在属性上,通过反射直接操作属性为属性赋值,不会使用set方法
- Autowired还可以用在set方法上,此时通过调用属性的set方法赋值
- Autowired修饰的属性默认是必须的,如果注入失败就会抛出异常;可以设置@Autowired(required=false)允许注入失败
@Qualifier(“bean的id”)
与@Autowired配合使用,当有多个满足属性类型要求的Bean时,可以用@Qualifier(“id”)来指定要注入的依赖。
class Address{
private String city;
private String address;
...
}
<bean id="addr1" class="com.bz.entity.Address">
<property name="city" value="郑州"/>
<property name="street" value="文化路"/>
</bean>
<bean id="addr2" class="com.bz.entity.Address">
<property name="city" value="郑州"/>
<property name="street" value="白庙路"/>
</bean>
class Person{
private String personName;
@Autowired
@Qualifier("addr1")
private Address addr;
...
}
@Value用于为简单属性赋值,还可以读取properties文件中的参数
jdbc.properties
url=jdbc:mysql://localhost:3306/spring?useUnicde=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=Asia/Shanghai
driverClassName=com.mysql.cj.jdbc.Driver
user=root
password=123456
JdbcProeprties.java
@Component
public class JdbcProperties{
//@Value("root")
@Value("${user}")
private String username;
@Value("${password}")
private String password;
@Value("${url}")
private String url;
@Value("${driverClassName}")
private String driverClassName;
...
}
行业标准:
- 自定义类型Service、Dao使用注解开发
- 第3方类型:数据库连接池、事务管理器…使用xml配置
2 基于注解的事务控制
使用Transactional注解描述类或者方法添加事务。
开发步骤:
-
创建原始对象
-
定义通知类(功能增强:事务控制)
Spring内置的DataSourceTransactionManager
-
配置通知类
<!-- 配置事务管理器--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDataSource"/> </bean> <tx:annotation-driven transaction-manager="txManager"/>
-
使用Transactional注解描述类或方法
@Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Transactional(readOnly=true) @Override public boolean login(String name, String password) { User user = userDao.selectUserByName(name); return user!= null && user.getPassword().equals(password); } public void register(String username,String password){ userDao.insertUser(new User(null,username,password)); } }
注意:
传统的Spring项目中,注解的事务控制使用较少,更多的还是使用xml配置的事务控制。但是在SpringBoot中大量的使用注解方式的事务配置。
3 Spring集成JUnit
直接使用JUnit测试Spring项目,需要手动编码获取context工厂,需要手动从工厂中获取对象,测试繁琐。
解决方案:使用Spring-test,简化了Spring项目的测试。
准备工作:导入JUnit、spring-test依赖
<!-- junit版本必须是4.12及以上-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
测试:
//开启Spring对Junit的支持
@RunWith(SpringRunner.class)
//设置配置文件的路径
@ContextConfiguration("classpath:applicationContext.xml")
public class ApplicationContextTest {
//自动从工厂中获取对象,为属性赋值
@Autowired
private ApplicationContext context;
@Autowired
private UserMapper userMapper;
@Autowired
private UserService userService;
...
}
小技巧:如果有多个测试类,多个测试类上方也有重复的配置,可以定义父类抽取共性
//开启Spring对Junit的支持
@RunWith(SpringRunner.class)
//设置配置文件的路径
@ContextConfiguration("classpath:applicationContext.xml")
public abstract class AbstractApplicationContextTest {
}
public class ApplicationContextTest extends AbstractApplicationContextTest{
@Autowired
private UserService userService;
...
}
4 再谈web.xml
4.1 web.xml标签的加载顺序
目前为止学习的标签:servlet filter listener context-param
加载顺序:
- context-param
- listener
- filter
- servlet
4.2 servlet的url-pattern配置形式以及优先级
url-pattern配置形式种类:
精确匹配
/student/showPageStudents
路径匹配
/* /student/*
扩展名匹配
*.jsp *.do *.action
缺省匹配
/ 在前3种都无法匹配时做默认匹配
优先级:精确匹配>路径匹配>扩展名匹配>缺省匹配
注意:
-
都是路径匹配时,最长路径优先
http://localhost:8989/spring-day05/student/b
/student/* 比/* 优先匹配到。
-
路径匹配和扩展名匹配不能混用
不能出现如下配置:
/student/*.do
/ * / *.jsp
另外注意:
- Filter一定早于Servlet执行
- 多个Filter执行的优先级只跟配置顺序有关,跟url-pattern的优先级没有关系
5 事务详解(概念重点)
5.1 read-only
read-only="true"表示只读事务,提高查询(检索)效率。read-only的默认值是false。
注意:只能应用到查询操作,不能应用到增删操操作上。
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 所有以show开头的方法,执行只读的事务-->
<tx:method name="show*" read-only="true"/>
<!-- 除了show开头的其它所有方法,执行读写的事务(false也是默认值)-->
<tx:method name="*" read-only="false"/>
</tx:attributes>
</tx:advice>
5.2 timeout
timeout超时机制(单位秒):事务执行时间超过指定的时长后,自动失败回滚。
timeout默认值-1:表示跟数据库的默认超时设置有关。
//通过sleep方法,模拟业务方法执行超时
public void removeUser(Integer id) {
try {
Thread.sleep(5000);//当前线程睡眠5s
} catch (InterruptedException e) {
e.printStackTrace();
}
userDao.deleteUserById(id);
}
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="show*" read-only="true"/>
<!-- 超时时间时间设置为3s-->
<tx:method name="*" timeout="3"/>
</tx:attributes>
</tx:advice>
@Test
//测试超时
public void testTimeout(){
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = (UserService)ctx.getBean("userServiceImpl");
userService.removeUser(1);
}
5.3 rollback-for和no-rollback-for
rollback-for:当发生rollback-for配置的异常类型的异常时,执行回滚。
no-rollback-for:当发生no-rollback-for配置的异常类型的异常时,不执行回滚。
Spring中默认:
RuntimeException及其子类(运行时异常)的异常执行回滚
Exception及其子类(编译时异常)的异常不会执行回滚
public void addUser(User u) {
userDao.insertUser(u);
// throw new RuntimeException("自定义未检查异常");//默认回滚
throw new Exception("自定义已检查异常");//默认不回滚
}
rollback-for和no-roollback-for的生效规则:
- 2个的属性本身没有优先级区别
- 程序产生的异常,与哪个属性中配置的异常类型继承关系更近就是用哪个配置
- 如果产生的异常和配置的异常类型没有继承关系,则使用默认规则
5.4 propagation(传播机制)
企业开发中,会出现业务层方法调用业务层方法的情况。propagation定义了一个业务方法被另外一个业务方法调用时的事务传播方式。
常用的事务传播机制:
传播机制 | 内部方法对事务的要求 | 特点 |
---|---|---|
REQUIRED | 必须有 | 如果外部有事务,则加入;外部没有事务,则新建 |
REQUIRES_NEW | 必须有 | 无论外部是否有事务,内部都会新建 |
SUPPORTS | 可有可无 | 如果外部有事务则加入;如果没有则以无事务状态运行 |
NOT_SUPPORTED | 必须没有 | 如果外部有事务,则挂起外部事务 |
MANDATORY | 必须有 | 如果外部没有事务,直接抛出异常 |
NEVER | 必须没有 | 如果外部有事务,直接抛出异常 |
NESTED | 必须有 | 如果外部没有事务,开启事务;如果外部有事务,记录当前的保存点;一旦出错,不会全部回滚,只回滚到保存点 |
5.5 isolation(隔离级别)
解决事务并发执行的问题,微观上,2个执行时间相近的事务相互影响的问题。
5.5.1 4种标准的隔离级别
隔离性在数据库的并发访问时得以体现,随着数据库并发事务处理能力的大大增强,数据库资源的利用率也会大大提高,从而提高了数据库系统的事务吞吐量,可以支持更多的用户并发访问。但并发事务处理也会带来一些问题,如:脏读、不可重复读、幻读。这些问题是和隔离性相伴相生的。下面一一解释其含义。
-
脏读
B事务正在对一条记录做修改,在这个事务提交前,这条记录的数据就处于不一致状态;这时,另一个事务A也来读取同一条记录,如果不加控制,第二个事务读取了这些未提交的(“脏”)数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象的叫作"脏读"(Dirty Reads)。
-
不可重复读
一个事务中多次执行相同的查询SQL,不同时刻读取的数据不同。要么发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫作“ 不可重复读”(Non-Repeatable Reads)。
-
幻读
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”(Phantom Reads)。
隔离级别 | 特点 | 存在的问题 | |
---|---|---|---|
READ-UNCOMMITTED | 读未提交 | 脏读、不可重复读、幻读 | |
READ-COMMITTED | 读已提交 | 只能读到另外一个事务提交后的数据。 | 不可重复读、幻读 |
REPEATABLE-READ | 可重复读 | 同一个事务下,前后两次结果一致 | 不同数据库不同,有的数据库会有幻读 |
SERIALIZABLE | 序列化读 | 串行 | 性能太差 |
Oracle只支持READ-COMMITTED和SERIALIZABLE 2种隔离级别。
MySQL支持4种。
实战时,使用读已提交即可。
5.5.2 MySQL下演示隔离级别
MySQL支持多种存储引擎:InnoDB(支持事务)、MyISAM、MEMORY
存储引擎:决定了数据库如何的保存管理数据。
- 确认MySQL使用的存储引擎
show engines;//查询数据库支持的存储引擎
show variables like 'default_storage_engine';//查询默认引擎
-
查询当前使用的隔离级别:
-
修改当前使用的隔离级别:
-
MySQL中开启事务和结束事务
>start transaction;//开启事务 >commit;//提交 >rollback;//回滚
隔离级别
准备工作:
create table t_account(
id int primary key,
money double
);
start transaction;
insert into t_account values(1,100);
insert into t_account values(2,200);
commit;
-
读未提交隔离级别
脏读:B事务未提交,A事务可以读取到B事务插入的数据
-
读提交隔离级别
B事务不提交,A事务读取不到。B事务提交后A事务就可以读取到。
仍存在的问题:不可重复读和幻读
-
可重复读隔离级别
-
序列化读
文章对应源码地址:https://download.csdn.net/download/qq_36827283/87386038