Spring Boot用于快速开启一个Spring项目:
spring-boot-starter-parent
:<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.3.RELEASEversion>
parent>
spring-boot-starter-web
和spring-boot-starter-jdbc
(connector-j需要另外导入,右键Diagrams显示依赖,可以看到依赖了什么库):<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>${boot.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
<version>2.1.3.RELEASEversion>
dependency>
@SpringBootApplication
代替@Configuration
、@ComponentScan
和@EnableAutoConfiguration
三个注解,@EnableTransactionManagement
表示支持事务,继承SpringBootServletInitializer
重写confiura()
方法以支持Spring MVC@SpringBootApplication
@EnableTransactionManagement
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
}
spring.datasource.url=jdbc:mysql://localhost:3306/sampledb?serverTimezone=GMT
spring.datasource.username=zhangyijun
spring.datasource.password=mysql1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.tomcat.max-idle=10
spring.datasource.tomcat.min-idle=8
spring.datasource.tomcat.max-wait=1000
spring.datasource.tomcat.max-active=100
spring.datasource.tomcat.test-on-borrow=true
spring.datasource.tomcat.validation-query=select 1
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
@RestController
和@RequestMapping
:@RestController
public class LoginController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@RequestMapping(value = {"/hello", "/index.html"})
public ModelAndView loginPage() {
return new ModelAndView("login");
}
@RequestMapping(value = "/loginCheck.html")
public ModelAndView loginCheck(HttpServletRequest request, LoginCommand loginCommand) {
boolean isValidUser = userService.hasMatchUser(loginCommand.getUserName(), loginCommand.getPassword());
if (!isValidUser) {
return new ModelAndView("login", "error", "用户名或密码错误");
} else {
User user = userService.findUserByUserName(loginCommand.getUserName());
user.setLastIp(request.getLocalAddr());
user.setLastVisit(new Date());
userService.loginSuccess(user);
request.getSession().setAttribute("user", user);
return new ModelAndView("main");
}
}
}
@Bean
注解方法的类 User:@Bean(name = "DefaultUser")
public User buildUser() {
return User.builder()
.userId(123)
.userName("in annotation @Bean")
.password("123")
.credits(123)
.lastIp("127.0.0.1")
.lastVisit(new Date())
.build();
}
T getBean(String name, Class requiredType)
方法获取bean:ApplicationContext context = new AnnotationConfigApplicationContext(User.class);
User user = context.getBean("DefaultUser", User.class);
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="date" class="java.util.Date"/>
<bean id="user" class="com.smart.domain.User"
p:userId="1"
p:userName="in xml file: beans.xml"
p:password="asd"
p:credits="123"
p:lastIp="1.1.1.1"
p:lastVisit-ref="date"/>
beans>
val context = new ClassPathXmlApplicationContext("classpath:beans.xml");
val user = context.getBean("user", User.class);
"classpath:beans.xml"
此处可以简写为"beans.xml"
beans.xml
放在了resources
文件夹下BeanFactory
和ApplicationContext
的生命周期类似,BeanFactory
在调用getBean(beanName)
的时候开始生命周期,ApplicationContext
在容器开启的时候一次性初始化所有Bean(可以通过@Lazy
注解实现懒加载):
postProcessBeforeInstantiation()
postProcessAfterInstantiation()
postProcessPropertyValues()
setBeanName()
setBeanFactory()
postProcessBeforeInitialization()
afterPropertiesSet()
postProcessAfterInitialization()
destroy()
方法
标签中定义init-method
和destory-method
,因为对象实例化和属性初始化的原因,这两个方法的生命周期都比较靠后@PreDestroy
和@PostConstrut
修饰方法public class User{
private String name;
public void setName(String name){
this.name = name;
}
}
<bean id="user" class="com.smart.domain.User" p:name="1"/>
<bean id="user1" class="com.smart.domain.User">
<constructor-arg index="0" type="int" value="1"/>
<constructor-arg index="1" type="java.lang.String" value="1"/>
<constructor-arg index="2" type="java.lang.String" value="1"/>
<constructor-arg index="3" type="int" value="1"/>
<constructor-arg index="4" type="java.lang.String" value="1123"/>
<constructor-arg index="5" type="java.util.Date" ref="date"/>
bean>
<bean id="userFactory" class="com.smart.factory.UserFactory"/>
<bean id="user" factory-bean="userFactory" factory-method="createUser"/>
<bean id="user" class="com.smart.factory.UserFactory" factory-method="createUser"/>
<bean id="boss" class="com.smart.attr.Boss">
<property name="car">
<bean class="com.smart.attr.Car">
<property name="maxSpeed" value="200"/>
<property name="price" value="2000.00"/>
bean>
property>
bean>
<bean id="boss" class="com.smart.attr.Boss">
<property name="car">
<null/>
property>
bean>
<bean id="boss" class="com.smart.attr.Boss">
<util:list id="carList" list-class="java.util.LinkedList">
<value>ferrarivalue>
<value>maserativalue>
<value>porschevalue>
util:list>
<property name="carSet">
<set>
<value>ferrarivalue>
<value>maserativalue>
<value>porschevalue>
set>
property>
<property name="carMap">
<map>
<entry key="Ferrari" value-ref="ferrari"/>
map>
property>
bean>
<bean id="boss" class="com.smart.attr.Car">
<property name="car.price" value="2000.00"/>
bean>
此时的car必须有一个实例,所以需要为car属性声明一个初始化对象:
public class Boss{
private Car car = new Car();
}
abstract=true
表示user
是一个模板,并不会实例化<bean id="user" class="com.smart.domain.User"
p:userId="1"
p:userName="in classpath beans.xml"
p:password="asd"
p:credits="123"
p:lastIp="1.1.1.1"
p:lastVisit-ref="date"
abstract="true"/>
<bean id="childUser" parent="user" p:userId="2"/>
depends-on
指定两个bean之间的实例化顺序,假设User
从UserConstanst
中读取配置信息,而此时有一个类ConstantInit
在初始化修改了UserConstanst
的配置信息,如果想User
实例化时得到UserConstanst
中的最新配置信息,可以使用depends-on
将User
的初始化放到ConstantInit
后<bean id="user" class="com.smart.domain.User"
p:userId="1"
p:userName="in classpath beans.xml"
p:password="asd"
p:credits="123"
p:lastIp="1.1.1.1"
p:lastVisit-ref="date"
depends-on="ConstantInit"/>
@Lazy
@Scope("prototype")
@Component("userDao")
public class UserDao{
...
}
@Component
可以用@Repository
替代标注Dao,用@Service
替代标注Service,用@Controller
替代标注Controller,因此此处等同于@Lazy
@Scope("prototype")
@Repository
public class UserDao{
...
}
<bean id="userDao"
class="com.smart.dao.UserDao"
lazy-init="true"
scope="prototype"/>
@Lazy
@Scope("prototype")
@Bean("userDao")
public UserDao createUserDao(){
return new UserDao();
}
@Autowired
@Qualifier("userDao")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public interface Hello {
void hello();
}
public class User{
@Override
public void hello() {
try {
Thread.sleep(1000);
System.out.println(getClass().getName() + ":hello");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MethodPerformance {
private long begin;
private String serviceMethod;
private static final ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<>();
public static void begin(String method) {
System.out.println("begin monitor");
MethodPerformance methodPerformance = new MethodPerformance(method);
performanceRecord.set(methodPerformance);
}
public static void end() {
System.out.println("end monitor");
MethodPerformance methodPerformance = performanceRecord.get();
methodPerformance.printPerformance();
}
private MethodPerformance(String serviceMethod) {
this.serviceMethod = serviceMethod;
begin = System.currentTimeMillis();
}
private void printPerformance() {
long end = System.currentTimeMillis();
System.out.println(serviceMethod + " cost " + (end - begin) + " millis.");
}
}
invoke()
的监听,需要实现到InvocationHandler
接口,在invoke()
中,方法调用的前后加入了性能监控的代码。获得代理对象调用的是Proxy.newProxyInstance()
方法,第一个入参为类加载器,第二个为类实现的接口,第三个参数传入InvocationHandler
接口的实现。public class JdkProxy implements InvocationHandler {
private Object target;
public JdkProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodPerformance.begin(method.getClass().getName() + "." + method.getName());
Object result = method.invoke(target, args);
MethodPerformance.end();
return result;
}
public static Object proxy(Object target) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new JdkProxy(target));
}
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
ApplicationContext context = new AnnotationConfigApplicationContext(User.class);
User target = context.getBean("DefaultUser", User.class);
Hello proxy = (Hello) JdkProxy.proxy(target);
proxy.hello();
}
begin monitor
com.smart.domain.User:hello
end monitor
java.lang.reflect.Method.hello cost 1000 millis.
MethodInterceptor
,以设置监听器的方式将MethodInterceptor
设置到Enhancer
中,当enhaner.create()
返回的代理调用方法的时候就会被拦截执行intercept()
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public static <T> T proxy(Class<T> clazz) {
CglibProxy cglibProxy = new CglibProxy();
Enhancer enhancer = cglibProxy.enhancer;
enhancer.setSuperclass(clazz);
enhancer.setCallback(cglibProxy);
return (T) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
MethodPerformance.begin(method.getClass().getName() + "." + method.getName());
Object result = methodProxy.invokeSuper(o, objects);
MethodPerformance.end();
return result;
}
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
val context = new AnnotationConfigApplicationContext(User.class);
User target = context.getBean("DefaultUser",User.class);
User proxy = CglibProxy.proxy(User.class);
proxy.hello();
proxy.setUserId(123);
target.print();
}
com.smart.domain.User$$EnhancerByCGLIB$$17f04a1c
,这是因为CGLib使用了字节码技术。由于是以动态创建子类的方法生成代理对象,private和final的方法无法被增强begin monitor
com.smart.domain.User$$EnhancerByCGLIB$$17f04a1c:hello
end monitor
java.lang.reflect.Method.hello cost 1003 millis.
begin monitor
end monitor
java.lang.reflect.Method.setUserId cost 0 millis.
User(userId=123, userName=in annotation @Bean, password=123, credits=123, lastIp=127.0.0.1, lastVisit=Tue Apr 02 08:58:41 GMT+08:00 2019)
Advice
来增强方法。调用ProxyFactory.setInterferces()
方法后默认以Jdk的动态代理技术来生成代理对象。(如果仍要使用CGLib,可以使用ProxyFactory.setOptimize(true)
方法)public class SpringProxy {
public static Object proxy(Object target, boolean useJdkProxy, Advice... advice) {
ProxyFactory factory = new ProxyFactory();
if (useJdkProxy) {
factory.setInterfaces(target.getClass().getInterfaces());
}
factory.setTarget(target);
for (int i = 0; i < advice.length; ++i) {
factory.addAdvice(i, advice[i]);
}
return factory.getProxy();
}
}
public class LogBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass() + "在" + new Date() + "调用了" + method.getName());
}
}
User target = context.getBean("DefaultUser", User.class);
User proxy = (User) SpringProxy.proxy(target, false, new LogBeforeAdvice());
proxy.hello();
class com.smart.domain.User 在 Tue Apr 02 09:27:51 GMT+08:00 2019 调用了 hello
com.smart.domain.User:hello
proxyTargetClass="false"
表示对接口进行代理,也就是使用JDK动态代理技术<bean id="user" class="com.smart.domain.User"
p:userId="1"
p:userName="in classpath beans.xml"
p:password="asd"
p:credits="123"
p:lastIp="127.0.0.1"
p:lastVisit-ref="date"/>
<bean id="logAdvice" class="com.smart.aop.LogBeforeAdvice"/>
<bean id="userProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.smart.domain.Hello"
p:interceptorNames="logAdvice"
p:proxyTargetClass="false"
p:target-ref="user"/>
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
val context = new ClassPathXmlApplicationContext("beans.xml");
Hello proxy = context.getBean("userProxy", Hello.class);
proxy.hello();
}
public class LogAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass() + " 在 " + new Date() + " 调用了 " + method.getName() + (returnValue == null ? "" : " 返回值为" + returnValue));
}
}
p:interceptorNames
中用逗号隔开多个Advice<bean id="logAdvice" class="com.smart.aop.advices.LogBeforeAdvice"/>
<bean id="logAdvice2" class="com.smart.aop.advices.LogAfterAdvice"/>
<bean id="userProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.smart.domain.Hello"
p:interceptorNames="logAdvice,logAdvice2"
p:proxyTargetClass="false"
p:target-ref="user"/>
<property name="interceptorNames">
<list>
<idref bean="logAdvice"/>
<idref bean="logAdvice2"/>
list>
property>
class com.smart.domain.User 在 Tue Apr 02 12:21:17 GMT+08:00 2019 调用了 hello
com.smart.domain.User:hello
class com.smart.domain.User 在 Tue Apr 02 12:21:18 GMT+08:00 2019 调用了 hello
public class LogInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
MethodPerformance.begin(invocation.getMethod().getName());
Object result = invocation.proceed();
MethodPerformance.end();
return result;
}
}
class com.smart.domain.User 在 Tue Apr 02 12:38:08 GMT+08:00 2019 调用了 hello
begin monitor
com.smart.domain.User:hello
end monitor
hello cost 1001 millis.
class com.smart.domain.User 在 Tue Apr 02 12:38:09 GMT+08:00 2019 调用了 hello
@Override
public void hello() throws RuntimeException {
throw new RuntimeException("hello runtime exception!");
}
public class AfterThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target, Exception e) {
System.out.println("1");
}
public void afterThrowing(Exception e) {
System.out.println("2");
}
public void afterThrowing() {
System.out.println("3");
}
public void afterThrowing(RuntimeException e) {
System.out.println("4");
}
}
PerformanceAccessible
,其中有方法setAccessible()
用于实现对性能监控的开关public interface PerformanceAccessible {
void setAccessible(boolean accessible);
}
DelegatingIntroductionInterceptor
并实现上面的PerformanceAccessible
接口。这样不仅方法setAccessible()
被嵌入到代理类中,而且invoke()
方法中还通过变量accessibleStatus.get()
来判断是否开启性能监控。public class LogIntroductionAdvice extends DelegatingIntroductionInterceptor implements PerformanceAccessible {
private ThreadLocal<Boolean> accessibleStatus = new ThreadLocal<>();
@Override
public void setAccessible(boolean accessible) {
accessibleStatus.set(accessible);
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Object o;
if (accessibleStatus.get() != null && accessibleStatus.get()) {
MethodPerformance.begin(mi.getMethod().getName());
o = super.invoke(mi);
MethodPerformance.end();
} else {
o = super.invoke(mi);
}
return o;
}
}
o = super.invoke(mi);
而不是o = mi.proceed();
p:proxyTargetClass="true"
和p:proxyInterfaces="com.smart.domain.PerformanceAccessible"
,因为引介增强只能通过创建子类的方法,于是要切换动态代理技术为CGLib<bean id="logAdvice" class="com.smart.aop.advices.LogIntroductionAdvice"/>
<bean id="userProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.smart.domain.PerformanceAccessible"
p:proxyTargetClass="true"
p:interceptorNames="logAdvice"
p:target-ref="user">
bean>
p:interfaces
中指定了引介增强的接口,而p:proxyInterfaces
中又设置了其他接口public static void main(String[] args) {
SpringApplication.run(Application.class, args);
val context = new ClassPathXmlApplicationContext("beans.xml");
Hello proxy = context.getBean("userProxy", User.class);
proxy.hello();
((PerformanceAccessible) proxy).setAccessible(true);
System.out.println("");
proxy.hello();
}
PerformanceAccessible
接口。log:hello
begin monitor
hello
end monitor
com.smart.domain.User.hello cost 1000 millis.
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.2version>
dependency>
@Aspect
注解。使用@Before
和@After
注解来增强hello()
方法(用JoinPoint
来获取连接点信息),这里开启了一个性能监控@Aspect
public class PerformanceAspect {
@Before("execution(* hello(..))")
public void beforeHello(JoinPoint point) {
System.out.println(Arrays.toString(point.getArgs())); //[]
System.out.println(point.getSignature()); //void com.smart.domain.User.hello()
System.out.println(point.getTarget().getClass().getName()); //com.smart.domain.User
System.out.println(point.getThis().getClass().getName()); //com.smart.domain.User$$EnhancerBySpringCGLIB$$8956fe50
MethodPerformance.begin("com.smart.domain.User.hello()");
}
@After("execution(* hello(..))")
public void afterHello() {
MethodPerformance.end();
}
}
@Berfore
对应MethodBeforeAdvice
,即前置增强@AfterReturning
对应AfterReturningAdvice
,即后置增强,通过returning
属性绑定返回值@Around
对应MethodInterceptor
,即环绕增强,用ProceedingJoinPoint
代替JoinPoint
访问连接点信息@AfterThrowing
对应ThrowAdvice
,即异常抛出增强,通过throwing
属性绑定异常对象@After
是@AfterReturning
和@AfterThrowing
的结合public static void main(String[] args) {
SpringApplication.run(Application.class, args);
ApplicationContext context = new AnnotationConfigApplicationContext(User.class);
User user = context.getBean("DefaultUser", User.class);
AspectJProxyFactory factory = new AspectJProxyFactory();
factory.setTarget(user);
factory.addAspect(PerformanceAspect.class);
Hello proxy = factory.getProxy();
proxy.hello();
}
@Aspect
切面的Bean创建代理并增强hello()
方法<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean class="com.smart.aop.aspect.PerformanceAspect"/>
class com.smart.domain.User$$EnhancerBySpringCGLIB$$8738de70
,获取的对象直接变成了代理对象public static void main(String[] args) {
SpringApplication.run(Application.class, args);
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user", User.class);
System.out.println(user.getClass());
}
@DeclareParent
对应IntroductionInterceptor
,即引介增强,在@Aspect
注解的类中加入用@DeclaerParent
注解的接口,defaultImpl
传入该接口的默认实现类。(这种方法实现的引介增强还不知道如何做到Advice那样控制的效果,好像没有对方法的监听)@DeclareParents(value = "com.smart.domain.User", defaultImpl = ByeImpl.class)
public Bye bye;
public class ByeImpl implements Bye {
@Override
public void bye() {
System.out.println("bye");
}
}
User user = context.getBean("user", User.class);
((Bye) user).bye();
bye
切点可以通过&&
、!
、||
运算符来进行复合运算
@Before("execution(* hello(..))") //匹配所有类中方法名为 hello的连接点,*号与方法名分开,表示任意返回值
@Before("execution(public * *(..))") //匹配所有public方法,第一个*指任意返回值,第二个指任意方法名
@Before("execution(* *To(..))") //匹配所有以To结尾的方法,第一个*指任意返回值,第二个指任意方法名前缀
@Before("execution(* com.smart.domain.User.*(..))") //匹配所有User的方法和User所有子类中继承自User的方法,第一个*指任意返回值,第二个指任意方法名
@Before("execution(* com.smart.domain.User+.*(..))") //匹配User和User所有子类中的所有方法
@Before("execution(* hello(String,*,Object+,..))") //方法入参指定第一个为String,第二个为任意一个类型,第三个为Object及其子类,第四个为任意长度任意类型
@Before("@annotation(com.smart.domain.Test)")
@Before("args(com.smart.domain.User)")
//等同于:
@Before("execution(* *(com.smart.domain.User+)")
也可以用于获取连接点参数:
// User中添加新的方法
public boolean update(String name, int id) {
this.userName = name;
this.userId = id;
return true;
}
此处args(name,id)
根据下面的增强方法的参数自动换为args(String,int)
,然后匹配到update(String name,int id)
方法
@AfterReturning(value = "target(com.smart.domain.User) && args(name,id)", returning = "result", argNames = "name,id,result")
public void afterUpdate(String name, int id, boolean result) {
System.out.println("name=" + name);
System.out.println("id=" + id);
System.out.println("result=" + result);
}
@Test
public class A{
}
public class B extends A{
}
需要增强的方法:
public void func(B b){
}
进行增强的代码:
@Before("@args(com.smart.domain.anno.Test)")
public void beforeFunc(){
}
此时@Test
的标注点A
高于func(B b)
的入参B
,此时切面不匹配任何类。
@Before("within(com.smart.domain.User)") //匹配User
@Before("within(com.smart.domain.*)") //匹配domain包中的所有类
@Before("within(com.smart.domain..*)") //匹配domain包以及子孙包中的所有类
@Before("@within(t)")
public void getAnnotation(Test t) {
System.out.println(t.getClass().getName()); //com.sun.proxy.$Proxy48,注解也被代理了
}
@Before("target(com.smart.domain.Hello)") //匹配实现了Hello接口的所有类的所有方法
IntroductionInterceptor
)@Aspect
public class PerformanceAspect {
@AfterReturning("this(com.smart.domain.Bye)")
public void testThis() {
System.out.println("im Bye!");
}
@DeclareParents(value = "com.smart.domain.User", defaultImpl = ByeImpl.class)
public Bye bye;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user", User.class);
user.hello();
((Bye) user).bye();
}
hello
im Bye!
ByeBye
//此处没有输出im Bye!
此处相当于:(将代理对象绑定到参数bye
上)
@AfterReturning(value = "this(bye)", argNames = "bye")
public void testThis(Bye bye) {
System.out.println("im Bye!");
}
脏读即一个事务读取另一个事务未提交的更改数据。
gantt
dateFormat h
section 事务A
查询余额为1000: 1, 2h
修改余额为500: 3, 2h
撤销事务,修改余额回1000: 7, 3h
section 事务B
查询余额为500: 5, 2h
汇入100,修改余额为600: 9, 2h
不可重复读即一个事务因为另外一个事务的提交而读到了两次不一样的数据。(行级锁)
gantt
dateFormat h
section 事务A
查询余额为1000: 1, 1h
修改余额为500: 3, 1h
section 事务B
查询余额为1000: 2, 1h
查询余额为500: 4, 1h
幻象读即一个事务在统计、查询等时候将另一个事务提交的新数据也算了进来。(表级锁)
gantt
dateFormat h
section 事务A
统计表中所有账户的总额为10000: 1, 2h
统计表中所有账户的总额为10100: 4, 2h
section 事务B
表中插入新数据,存款为100: 3, 1h
第一类数据更新即一个事务撤销时把另一个事务提交的有效数据覆盖了。
gantt
dateFormat h
section 事务A
查询余额为1000: 1, 2h
取出500: 3, 2h
撤销事务,余额恢复1000: 9, 3h
section 事务B
查询余额为500: 5, 2h
汇入100并提交: 7, 2h
第二类数据更新即一个事务提交时把另一个事务提交的有效数据覆盖了。
gantt
dateFormat h
section 事务A
查询余额为1000: 1, 2h
取出100并提交(修改为900): 5, 2h
section 事务B
查询余额为1000: 3, 2h
汇入100并提交(修改为1100): 7, 2h
lock table in row exclusive mode
或者MySQL的select .. for update
获得的行独占锁,update、delete、insert默认获得独占锁)不允许其他事务读和写。select .. for update
、lock table in row share mode
或者MySQL的select .. lock in share mode
获得的行共享锁)允许其他事务读取,不允许写。Mysql数据库事务的隔离级别和锁的实现原理分析
数据库并发的五个问题以及四级封锁协议与事务隔离的四个级别
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*User*" rollback-for="Exception" isolation="SERIALIZABLE" propagation="REQUIRED"
read-only="false" no-rollback-for="" timeout="-1"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id=" helloPoint " expression="execution(* com.smart.service.UserService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref=" helloPoint "/>
aop:config>
就像使用增强一样使用事务管理。其中tx:method的属性:
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务则新建一个,有则加入到这个事务中 |
PROPAGATION_SUPPORTS | 支持当前事务,如果没有则以非事务方式执行 |
PROPAGATION_MANDATORY | 使用当前事务,如果没有则抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务则把当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务则把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式执行操作,如果当前存在事务则抛出异常 |
PROPAGATION_NESTED | 若当前存在事务则在嵌套事务内执行,若没有则新建 |
DEFAULT
默认级别配置文件中加入以下两句:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>
transaction-manager="transactionManager"
proxy-target-class
指定代理方式是按类还是接口order
指定织入顺序@Transactional
的属性和使用xml配置相差无几:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
@Transactional
可以标注在类或方法上,此处方法标注的timeout = 5
会覆盖类注解中的timeout = -1
(默认值)
@Transactional
@Service
public class UserService {
@Transactional(timeout = 5)
public void loginSuccess(User user) {
user.setCredits(5 + user.getCredits());
userDao.updateLoginInfo(user);
loginLogDao.insertLoginLog(LoginLog.builder()
.userId(user.getUserId())
.ip(user.getLastIp())
.loginDate(user.getLastVisit())
.build());
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
<version>2.1.3.RELEASEversion>
dependency>
p:mappingLocations
为对象映射文件,路径在resource下要加classpath:
、在java包里面写要加classpath*:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd ">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"
p:mappingLocations="classpath:hbm/*.xml"
p:dataSource-ref="dataSource"/>
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate5.HibernateTemplate"
p:sessionFactory-ref="sessionFactory"/>
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory"/>
<tx:annotation-driven proxy-target-class="true"/>
beans>
name
、表名table
、主键id
等等,type
是映射类型
<hibernate-mapping>
<class name="com.smart.domain.User" table="t_user">
<meta attribute="user-description">
这里是使用Hibernate时,User的映射文件
meta>
<id name="userId" type="int" column="user_id">
<generator class="native"/>
id>
<property name="userName" type="string" column="user_name"/>
<property name="credits" type="int" column="credits"/>
<property name="password" type="string" column="password"/>
<property name="lastVisit" type="date" column="last_visit"/>
<property name="lastIp" type="string" column="last_ip"/>
class>
hibernate-mapping>
@ImportResource("classpath:hibernate.xml")
导入hibernate配置文件,使用泛型提供增删查改方法。整合SSH的时候可能出现update、save、delete等方法执行无效的问题,hibernate需要在事务环境下运行,所以此处标注上了@Transactional
@Transactional
@ImportResource("classpath:hibernate.xml")
public class BaseDao<T> implements BaseDaoOperation<T> {
private HibernateTemplate hibernateTemplate;
@Autowired
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
public HibernateTemplate getHibernateTemplate() {
return hibernateTemplate;
}
@Override
public Serializable save(T t) {
return hibernateTemplate.save(t);
}
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void update(T t) {
hibernateTemplate.update(t);
}
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void delete(T t) {
hibernateTemplate.delete(t);
}
@Override
@SuppressWarnings("unchecked")
public T find(Serializable id) {
Class<T> clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
return hibernateTemplate.get(clazz, id);
}
@Override
public List<T> find(T example) {
return hibernateTemplate.findByExample(example);
}
}
REPEATABLE_READ
这一点十分重要@Repository
@Repository
public class UserDao extends BaseDao<User> {
}
@RestController
public class Controller {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
/**
* GET --> 使用id来查找用户
*
* @param id 用户id
* @return 用户数据
*/
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
@ResponseBody
public ResultBean<User> find(@PathVariable("id") int id) {
User user = userDao.find(id);
return user != null ? ResultBean.ok(user) : ResultBean.no("用户不存在");
}
/**
* POST JSON --> 保存用户
*
* @param entity 用户
* @return 保存结果
*/
@RequestMapping(value = "/user/save", method = RequestMethod.POST, produces = "application/json")
@ResponseBody
public ResultBean<String> save(HttpEntity<User> entity) {
User user = entity.getBody();
if (user != null) {
int key = (int) userDao.save(user);
return ResultBean.ok("主键为" + key);
} else {
return ResultBean.no("请检查输入");
}
}
/**
* GET --> 修改用户名
*
* @param name 新用户名
* @param id 要修改的用户id
* @return 新的用户数据
*/
@RequestMapping(value = "/user/update/{id}", method = RequestMethod.GET)
@ResponseBody
public ResultBean<User> update(@RequestParam("name") String name, @PathVariable("id") int id) {
User user = userDao.find(id);
if (user != null && !name.equals("")) {
user.setName(name);
userDao.update(user);
return ResultBean.ok(user);
} else if (user == null) {
return ResultBean.no("用户不存在");
} else if (name.equals("")) {
return ResultBean.no("名字不可为空");
} else {
return ResultBean.no("失败");
}
}
/**
* GET --> 用id删除用户
*
* @param id 用户id
* @return 删除用户的数据
*/
@RequestMapping(value = "user/delete/{id}", method = RequestMethod.GET)
@ResponseBody
public ResultBean<User> delete(@PathVariable("id") int id) {
User user = userDao.find(id);
if (user != null) {
userDao.delete(user);
return ResultBean.ok(user);
} else {
return ResultBean.no("用户不存在");
}
}
/**
* GET --> 用名字寻找用户
*
* @param key 名字
* @return 符合的用户列表
*/
@RequestMapping(value = "user/list", method = RequestMethod.GET)
@ResponseBody
public ResultBean<List<User>> list(@RequestParam(value = "key", required = false) String key) {
if (key == null) {
return ResultBean.ok(userDao.all());
} else {
List<User> users = userDao.find(User.builder().name(key).build());
return users.size() == 0 ? ResultBean.no("用户列表为空") : ResultBean.ok(users);
}
}
}
@Entity
和@Table("表名")
,属性标注@Column("列名")
,主键标注上@Id
,自增的话还需要标注上@GeneratedValue(strategy = GenerationType.IDENTITY)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private int id;
@Column(name = "user_name")
private String name;
@Column(name = "password")
private String password;
}
p:packagesToScan="com.smart.domain"
指定要扫描注解的包,这时使用xml配置的User.hbm.xml
和使用注解配置的User可以同时工作,冲突的时候会默认使用xml的配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd ">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"
p:packagesToScan="com.smart.domain"
p:mappingLocations="classpath:hbm/*.xml"
p:dataSource-ref="dataSource"/>
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate5.HibernateTemplate"
p:sessionFactory-ref="sessionFactory"/>
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory"/>
<tx:annotation-driven proxy-target-class="true"/>
beans>
java.lang.ClassCastException: org.springframework.orm.jpa.EntityManagerHolder cannot be cast to org.springframework.orm.hibernate5.SessionHolder
,请在@SpringBootApplication()
中加上exclude = HibernateJpaAutoConfiguration.class