Spring事务管理
声明式事务:
1 通过XML配置,声明某方法的事务特征
2、通过注解,声明某方法的事务特征,注解@Transactional
@Transactional 注解参数讲解
isolation :隔离级别;propagation :传播机制
传播机制比较难理解,这里粗略说一下
假如在A方法中,调用了B方法,而B方法是事务操作,则A为B的外部事务。
编程式事务:
通过TransactionTemplate管理事务,并通过它执行数据库的操作
package com.nowcoder.community.service;
import com.nowcoder.community.dao.AlphaDao;
import com.nowcoder.community.dao.DiscussPostMapper;
import com.nowcoder.community.dao.UserMapper;
import com.nowcoder.community.entity.DiscussPost;
import com.nowcoder.community.entity.User;
import com.nowcoder.community.util.CommunityUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Date;
@Service
//@Scope("prototype")
public class AlphaService {
@Autowired
private AlphaDao alphaDao;
@Autowired
private UserMapper userMapper;
@Autowired
private DiscussPostMapper discussPostMapper;
@Autowired
private TransactionTemplate transactionTemplate;
public AlphaService() {
// System.out.println("实例化AlphaService");
}
@PostConstruct
public void init() {
// System.out.println("初始化AlphaService");
}
@PreDestroy
public void destroy() {
// System.out.println("销毁AlphaService");
}
public String find() {
return alphaDao.select();
}
// REQUIRED: 支持当前事务(外部事务),如果不存在则创建新事务.
// REQUIRES_NEW: 创建一个新事务,并且暂停当前事务(外部事务).
// NESTED: 如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),否则就会REQUIRED一样.
//isolation 隔离性 propagation 传播机制
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public Object save1() {
// 新增用户
User user = new User();
user.setUsername("alpha");
user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
user.setPassword(CommunityUtil.md5("123" + user.getSalt()));
user.setEmail("[email protected]");
user.setHeaderUrl("http://image.nowcoder.com/head/99t.png");
user.setCreateTime(new Date());
userMapper.insertUser(user);
// 新增帖子
DiscussPost post = new DiscussPost();
post.setUserId(user.getId());
post.setTitle("Hello");
post.setContent("新人报道!");
post.setCreateTime(new Date());
discussPostMapper.insertDiscussPost(post);
Integer.valueOf("abc");
return "ok";
}
//通过TransactionTemplate进行局部的事务回滚
public Object save2() {
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return transactionTemplate.execute(new TransactionCallback
什么是cookie,即浏览器的一种缓存数据,为了弥补http无状态的缺陷。
对于注册的验证码功能,采用特定的验证码生成工具生成验证码;在这里,项目中并没有采用某一个变量去接收这个验证码的值,而是采用了cookie,即每个客户端都拥有它们自己的cookie,假如采用全局变量去接收,则很容易产生并发的问题;采用cookie即将每个用户都隔离开来,各自用各自浏览器的cookie。
对于登陆状态这个功能,主要就是拿浏览器的cookie去redis中找状态是否过期。同样的,并不会用项目中的某个变量去接收ticket,这样同上会产生并发问题。cookie中存储ticket可以保证不同用户都可以存储它们自己的ticket,并可以拿其去进行验证
cookie的用法如下:
cookie.setPath是一个方法,用于设置cookie的path属性。path属性指定了哪些URL的请求可以携带cookie。如果不设置path属性,那么默认值是创建cookie的应用的路径,这意味着只有同一个应用可以访问这个cookie。如果设置了path属性,那么所有以该路径为前缀的URL都可以访问这个cookie。例如,如果设置了cookie.setPath(“/test”),那么/test, /test/, /test/a, /test/b等URL都可以访问这个cookie,但是/, /doc, /fr/test等URL则不能访问这个cookie。
response.addCookie是一个方法,用于将cookie添加到HTTP响应中,从而发送给客户端浏览器。这个方法需要一个Cookie对象作为参数,Cookie对象可以用Cookie(name, value)构造器创建,其中name和value是字符串类型。
例如,如果要创建一个名为user,值为Tom的cookie,并将其添加到响应中,可以使用以下代码:
Cookie cookie = new Cookie(“user”, “Tom”); response.addCookie(cookie);
这样,客户端浏览器就会收到一个包含user=Tom的Set-Cookie头,并将其保存在本地。
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
//设置可以携带cookie的路径
cookie.setPath(contextPath);
//设置cookie的生存时间
cookie.setMaxAge(expiredSeconds);
//将cookie添加到HTTP响应中,从而发送给客户端浏览器
response.addCookie(cookie);
拦截器示例:
1、定义拦截器,实现HandlerInterceptor,根据自己的需求重写prehandle等方法
2、配置拦截器,为他指定拦截、排除的路径
拦截器应用:
1、在请求开始时查询登录用户
2、在本次请求中持有用户数据
3、在模板视图上显示用户数据
4、在请求结束时清理用户数据
示例如下代码所示
package com.nowcoder.community.controller.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class AlphaInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(AlphaInterceptor.class);
// 在Controller之前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.debug("preHandle: " + handler.toString());
return true;
}
// 在Controller之后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.debug("postHandle: " + handler.toString());
}
// 在TemplateEngine之后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.debug("afterCompletion: " + handler.toString());
}
}
需要配置webconfig使得拦截器生效
package com.nowcoder.community.config;
import com.nowcoder.community.controller.interceptor.AlphaInterceptor;
import com.nowcoder.community.controller.interceptor.LoginRequiredInterceptor;
import com.nowcoder.community.controller.interceptor.LoginTicketInterceptor;
import com.nowcoder.community.controller.interceptor.MessageInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AlphaInterceptor alphaInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(alphaInterceptor)
//excludePathPatterns排除以下路径,在以下路径拦截器并不会作用
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg") //以下路径拦截器生效
.addPathPatterns("/register", "/login");
}
}
输入请求路径login,看看日志输出,检查拦截器是否发生作用
打印出handler,说明login触发了LoginController类内部的方法,从而拦截器效果
具体拦截器的操作见上一篇文章
ThreadLocal
在这个项目中,采用ThreadLocal来存储用户的信息,确保每个用户线程访问到的都是自己的User对象,不受到其他用户线程的影响
/**
* 持有用户信息,用于代替session对象.
*/
@Component
public class HostHolder {
private ThreadLocal users = new ThreadLocal<>();
public void setUser(User user) {
users.set(user);
}
public User getUser() {
return users.get();
}
public void clear() {
users.remove();
}
}
我们可以看看Threadlocal.set和get方法的源码,可以看出对Threadlocal变量操作,都是对于当前线程的变量进行操作,具有隔离性,其他线程无法操作当前线程内Threadlocal的值
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
即将java字符串,对象等转换为json对象,或将json对象转换为java字符串对象等,输出结果如下图所示
//转换为json字符串
public static String getJSONString(int code, String msg, Map map) {
JSONObject json = new JSONObject();
json.put("code", code);
json.put("msg", msg);
if (map != null) {
for (String key : map.keySet()) {
json.put(key, map.get(key));
}
}
return json.toJSONString();
}
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("name", "zhangsan");
map.put("age", 25);
System.out.println(getJSONString(0, "ok", map));
}