主要是用于spring和MongoDB关于事务的整合,自己整合的,如果觉得有问题的地方,欢迎指出来
相关的jar包:
UTF-8
5.0.4.RELEASE
4.12
1.2.3
3.4.1
5.1.26
org.springframework
spring-aspects
${spring.version}
org.springframework.data
spring-data-mongodb
2.0.8.RELEASE
org.mongodb
mongo-java-driver
3.8.0
注入spring MongoConfig.java
package com.tree.common.config;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoDatabase;
/**
* @ClassName: MongoConfig
* @Description: mongodb配置 注入spring
* @author chen shihua
* @date 2018年7月10日 下午1:35:00
* @version V1.0
*/
@Configuration
@EnableMongoRepositories("mongodb.properties")
public class MongoConfig {
private static Logger LOGGER = LoggerFactory.getLogger(MongoConfig.class);
/**
* @Fields host : 服务器ip
*/
@Value("${mongo.host}")
private String hostports;
/**
* @Fields port : 端口号
*/
@Value("${mongo.port}")
private String port;
/**
* @Fields maxConnect : 一个线程变为可用的最大阻塞数
*/
@Value("${mongo.connectionsPerHost}")
private Integer connectionsPerHost;
/**
* @Fields maxWaitThread : 线程队列数,它以上面connectionsPerHost值相乘的结果就是线程队列最大值
*/
@Value("${mongo.maxWaitThread}")
private Integer maxWaitThread;
/**
* @Fields maxTimeOut : 连接超时时间(毫秒)
*/
@Value("${mongo.connectTimeout}")
private Integer connectTimeout;
/**
* @Fields maxWaitTime : 最大等待时间
*/
@Value("${mongo.maxWaitTime}")
private Integer maxWaitTime;
/**
* @Fields socketTimeout : scoket超时时间
*/
@Value("${mongo.socketTimeout}")
private Integer socketTimeout;
/**
* @Fields username : 用户名
*/
@Value("${mongo.username}")
private String username;
/**
* @Fields password : 密码
*/
@Value("${mongo.password}")
private String password;
/**
* @Fields database : 数据库
*/
@Value("${mongo.database}")
private String database;
/**
* @Fields collection : TODO(用一句话描述这个变量表示什么)
*/
@Value("${mongo.collection}")
private String collection;
@Bean
public MongoClient mongoClient() {
MongoClient mongoClient = null;
MongoClientOptions.Builder build = new MongoClientOptions.Builder();
build.connectionsPerHost(Integer.valueOf(connectionsPerHost));
build.threadsAllowedToBlockForConnectionMultiplier(maxWaitThread);
build.connectTimeout(Integer.valueOf(connectTimeout) * 1000);
build.maxWaitTime(Integer.valueOf(maxWaitTime) * 1000);
build.socketTimeout(socketTimeout);
build.socketKeepAlive(true);
MongoClientOptions options = build.build();
try {
List addrs = new ArrayList();
for (String hostport : hostports.split(", *")) {
if (StringUtils.isBlank(hostport)) {
continue;
}
hostport = hostport.trim();
ServerAddress serverAddress = new ServerAddress(hostport.split(":")[0],Integer.valueOf(hostport.split(":")[1]));
addrs.add(serverAddress);
}
MongoCredential credential = MongoCredential.createScramSha1Credential(username, database, password.toCharArray());
mongoClient = new MongoClient(addrs,credential, options);
LOGGER.info("20-02 " + "" + " 10-03 " + "admin" + " mongodb客户端创建成功 " + "admin");
} catch (Exception e) {
LOGGER.info("20-02 " + "" + " 10-03 " + "admin" + " mongodb客户端创建失败 " + "admin");
e.printStackTrace();
}
return mongoClient;
}
@Bean
public ClientSession clientSession(MongoClient mongoClient) {
ClientSession clientSession = mongoClient.startSession();
return clientSession;
}
@Bean
public MongoDatabase mongoDatabase(MongoClient mongoClient) {
MongoDatabase mongoDatabase = mongoClient.getDatabase(database);
return mongoDatabase;
}
@Bean()
public MongoTemplate mongoTemplate(MongoClient mongoClient){
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient, database);
return mongoTemplate;
}
// @Bean
// public MongoCollection mongoCollection(MongoDatabase mongoDatabase) {
// MongoCollection mongoCollection = mongoDatabase.getCollection(collection);
// return mongoCollection;
// }
//
}
数据库连接资源文件 mongodb.properties
#mongoDB连接配置
mongo.host=127.0.0.1
mongo.port=27017
mongo.username=name
mongo.password=123456
mongo.database=db
#一个线程变为可用的最大阻塞数
mongo.connectionsPerHost=8
#线程队列数,它以上面connectionsPerHost值相乘的结果就是线程队列最大值
mongo.maxWaitThread=4
#连接超时时间(毫秒)
mongo.connectTimeout=10000
#最大等待时间
mongo.maxWaitTime=1500
#自动重连
mongo.autoConnectRetry=true
#scoket保持活动
mongo.socketKeepAlive= true
#scoket超时时间
mongo.socketTimeout=1500
#读写分离
mongo.slaveOk=true
数据操作工具类 MongoSupport.java
package com.tree.common.config;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.CollectionUtils;
import com.alibaba.fastjson.JSONObject;
import com.mongodb.MongoSocketReadTimeoutException;
import com.mongodb.client.ClientSession;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.tree.common.templete.Pager;
import com.tree.common.util.MapUtils;
import com.tree.common.util.SpringUtil;
/**
* @ClassName: MongoSupport
* @Description: mongoDB 数据交互支持
* @author chen shihua
* @date 2018年7月10日 下午5:31:49
* @version V1.0
* @param
*/
public class MongoSupport {
/**
* @Fields mongoDatabase : 数据库对象
*/
private MongoDatabase mongoDatabase;
/**
* @Fields mongoCollection : 表集合对象
*/
private MongoCollection mongoCollection = null;
/**
* @Fields clazz : 泛型类
*/
private final Class clazz;
MongoTemplate mongoTemplate;
@SuppressWarnings("unchecked")
public MongoSupport() {
mongoDatabase = SpringUtil.getBean("mongoDatabase");
mongoTemplate = SpringUtil.getBean("mongoTemplate");
this.clazz = (Class)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
mongoCollection = mongoDatabase.getCollection(clazz.getSimpleName().toLowerCase());
}
/**
* @Title: selectAll
* @Description: 根据条件查询数据
* @param query 查询对象 可以query.addCriteria(criteriaDefinition) 添加查询
* @return List 查询到的数据
* @author chen shihua
* @throws
*/
public List selectAll(Query query) {
try {
return mongoTemplate.find(query, this.clazz);
} catch (MongoSocketReadTimeoutException e) {
//连接超时,再进行一遍查询
return mongoTemplate.find(query, this.clazz);
}
}
/**
* @Title: selectCount
* @Description: 根据条件查询总条数
* @param query 查询对象 可以query.addCriteria(criteriaDefinition) 添加查询
* @return long 总条数
* @author chen shihua
* @throws
*/
public long selectCount(Query query) {
try {
return mongoTemplate.count(query, this.clazz);
} catch (MongoSocketReadTimeoutException e) {
return mongoTemplate.count(query, this.clazz);
}
}
/**
* @Title: selectPage
* @Description: 根据条件分页查询数据
* @param page 分页对象
* @param query 查询条件
* @return void 返回类型
* @author chen shihua
* @throws
*/
public void selectPage(Pager page,Query query) {
long count = this.selectCount(query);
page.setTotal(count);
int first = page.getFirst();
int pageSize = page.getPageSize();
query.skip(first).limit(pageSize);
List datas = new ArrayList<>();
try {
datas = this.selectAll(query);
} catch (MongoSocketReadTimeoutException e) {
datas = this.selectAll(query);
}
page.setData(datas);
}
/**
* @Title: seleceById
* @Description: 根据id查询数据
* @param id 主键id
* @return T 返回类型
* @author chen shihua
* @throws
*/
@SuppressWarnings("unchecked")
public T seleceById(String id) {
ClientSession clientSession = MongoTransactionHandle.getClientSession();
Document query = new Document("_id", new ObjectId(id));
FindIterable iterable = null;
try {
iterable = (FindIterable) mongoCollection.find(clientSession, query, Document.class);
} catch (MongoSocketReadTimeoutException e) {
iterable = (FindIterable) mongoCollection.find(clientSession, query, Document.class);
}
MongoCursor cursor = iterable.iterator();
List result = new ArrayList();
while (cursor.hasNext()) {
Document object = cursor.next();
result.add(object);
}
if(CollectionUtils.isEmpty(result)) {
return null;
}
Document document = result.get(0);
String json = document.toJson();
Map map = JSONObject.parseObject(json, Map.class);
map.remove("_id");
map.put("id", id);
T t = (T) MapUtils.mapToObject(map, this.clazz);
return t;
}
/**
* @Title: update
* @Description: 根据查询条件修改数据
* @param query 查询条件
* @param update 修改数据
* @return void 返回类型
* @author chen shihua
* @throws
*/
public void update(Map query, Map update) {
ClientSession clientSession = MongoTransactionHandle.getClientSession();
Document queryDocument= new Document(query);
Document modifiers = new Document();
update.remove("id");
Document updateDocument = new Document(update);
modifiers.append("$set", updateDocument);
try {
mongoCollection.updateMany(clientSession, queryDocument, modifiers );
} catch (MongoSocketReadTimeoutException e) {
mongoCollection.updateMany(clientSession, queryDocument, modifiers );
}
}
/**
* @Title: updateById
* @Description: 根据id修改数据
* @param id 主键id
* @param update 修改数据
* @return void 返回类型
* @author chen shihua
* @throws
*/
public void updateById(String id,Map update) {
Map query = new HashMap<>();
query.put("_id", new ObjectId(id));
this.update(query , update);
}
/**
* @Title: insert
* @Description: 添加单条数据
* @param entity 数据对象
* @return void 返回类型
* @author chen shihua
* @throws Exception
* @throws
*/
public String insertOne(T entity){
Map param = MapUtils.getJaveBeanToMap(entity);
param.remove("id");
Document document = new Document(param);
ClientSession clientSession = MongoTransactionHandle.getClientSession();
try {
mongoCollection.insertOne(clientSession, document);
} catch (MongoSocketReadTimeoutException e) {
mongoCollection.insertOne(clientSession, document);
}
ObjectId objectId = (ObjectId) document.get("_id");
return objectId.toString();
}
/**
* @Title: insertList
* @Description: 添加多条数据
* @param list 数据集合
* @return void 返回类型
* @author chen shihua
* @throws
*/
public void insertList(List list) {
ClientSession clientSession = MongoTransactionHandle.getClientSession();
List documents = new ArrayList<>();
for (T entity : list) {
Map param = MapUtils.getJaveBeanToMap(entity);
MapUtils.mapClearEmpty(param);
Document document = new Document(param);
documents.add(document);
}
try {
mongoCollection.insertMany(clientSession, documents );
} catch (MongoSocketReadTimeoutException e) {
mongoCollection.insertMany(clientSession, documents );
}
}
/**
* @Title: delete
* @Description: 根据条件删除数据
* @return void 返回类型
* @author chen shihua
* @throws
*/
public void delete(Map param) {
ClientSession clientSession = MongoTransactionHandle.getClientSession();
Document document = new Document(param);
try {
mongoCollection.deleteMany(clientSession, document);
} catch (MongoSocketReadTimeoutException e) {
mongoCollection.deleteMany(clientSession, document);
}
}
/**
* @Title: deleteById
* @Description: 根据主键id删除数据
* @param id 主键id
* @return void 返回类型
* @author chen shihua
* @throws
*/
public void deleteById(String id) {
Map map = new HashMap<>();
map.put("_id",new ObjectId(id));
this.delete(map);
}
/**
* 多个id删除数据
* @param ids id集合
* @throws
*/
public void deleteByIdList(List ids) {
ClientSession clientSession = MongoTransactionHandle.getClientSession();
List idList = new ArrayList<>();
for (String id : ids) {
idList.add(new ObjectId(id));
}
Document document = new Document("$in",idList);
Document options = new Document("_id",document);
try {
mongoCollection.deleteMany(clientSession, options);
} catch (MongoSocketReadTimeoutException e) {
mongoCollection.deleteMany(clientSession, options);
}
}
}
map工具类 MapUtils.java
package com.tree.common.util;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.converters.DateConverter;
/**
* @ClassName: MapUtil
* @Description: map工具类
* @author chen shihua
* @date 2018年7月10日 下午3:46:20
* @version V1.0
*/
public class MapUtils {
/**
* @Title: getJaveBeanToMap
* @Description: javabean转Map
* @return Map 返回类型
* @author chen shihua
* @throws
*/
public static Map getJaveBeanToMap(Object obj) {
if (obj == null) {
return null;
}
Map map = new HashMap();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
System.out.println("key:"+key);
// 过滤class属性
if (!key.equals("class")) {
// 得到property对应的getter方法
Method getter = property.getReadMethod();
Object value = getter.invoke(obj);
map.put(key, value);
}
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("java对象转Map失败:"+e.getMessage());
}
return map;
}
public static Object mapToObject(Map map, Class> beanClass){
try {
if (map == null)
return null;
ConvertUtils.register(new DateConverter(null), java.util.Date.class);
Object obj = beanClass.newInstance();
BeanUtils.populate(obj, map);
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* @Title: mapClearEmpty
* @Description: 去除map中值为空置的键值对
* @return void 返回类型
* @author chen shihua
* @throws
*/
public static void mapClearEmpty(Map map) {
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
Object value = map.get(key);
if(value == null) {
iterator.remove();
}
}
}
}
注入bean的工具类 SpringUtil.java
package com.tree.common.util;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Service;
/**
* @ClassName: SpringUtils
* @Description: 注入bean的工具类
* @author chen shihua
* @version V1.0
*
*/
@Service("springUtil")
public final class SpringUtil implements BeanFactoryPostProcessor {
// Spring应用上下文环境
private static ConfigurableListableBeanFactory beanFactory;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringUtil.beanFactory = beanFactory;
}
/**
* @Title: getBean
* @Description: 获取对象
* @return Object 一个以所给名字注册的bean的实例
* @throws
*/
@SuppressWarnings("unchecked")
public static T getBean(String name) throws BeansException {
return (T) beanFactory.getBean(name);
}
/**
*
* @Title: getBean
* @Description: 获取类型为requiredType的对象
* @return T 返回类型
* @throws org.springframework.beans.BeansException
*/
public static T getBean(Class clz) throws BeansException {
T result = (T) beanFactory.getBean(clz);
return result;
}
/**
* @Title: containsBean
* @Description: 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
* @return boolean 返回类型
* @throws
*/
public static boolean containsBean(String name) {
return beanFactory.containsBean(name);
}
/**
* @Title: isSingleton
* @Description: 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
* @return boolean 返回类型
* @throws
*/
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
return beanFactory.isSingleton(name);
}
/**
* @Title: getType
* @Description: 注册对象的类型
* @return Class 注册对象的类型
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*/
public static Class> getType(String name) throws NoSuchBeanDefinitionException {
return beanFactory.getType(name);
}
/**
* @Title: getAliases
* @Description: 如果给定的bean名字在bean定义中有别名,则返回这些别名
* @return String[] 返回类型
* @throws NoSuchBeanDefinitionException
*/
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
return beanFactory.getAliases(name);
}
}
拦截器,用去开启事务和提交事务 TransactionInterceptor.java
package com.tree.common.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.mongodb.MongoClient;
import com.tree.common.base.BaseController;
import com.tree.common.config.MongoTransactionHandle;
import com.tree.common.constant.ResultCode;
import com.tree.common.util.SendMsgUtil;
import com.tree.common.util.SpringUtil;
import com.tree.modules.info.entity.User;
import com.tree.modules.info.entity.vo.LoginUser;
/**
* @ClassName: TransactionInterceptor
* @Description: 事务拦截器
* @author chen shihua
* @date 2018年7月10日 下午4:14:39
* @version V1.0
*/
public class TransactionInterceptor extends BaseController implements HandlerInterceptor {
/**
* 该方法会在控制器方法前执行,其返回值表示是否中断后续操作
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String uri = request.getRequestURI();
if(!uri.contains("/xueshu/login") && !uri.contains("/xueshu/register")) {
User user = LoginUser.get(request.getSession());
if(user == null) {//用户未登录
resultCodeAndMsg(ResultCode.NOT_LOG_IN, "用户未登录");
SendMsgUtil.sendJsonMessage(response, result());
return false;
}else {
// String token = request.getParameter("token");
String userId = user.getId();
// Long loginTime = user.getLoginTime();
//验证token
// String tokenMd5 = MD5Util.getMD5(userId+"---"+loginTime);
// if(StringUtils.isBlank(token)) {
// resultCodeAndMsg(ResultCode.ERROR, "令牌验证失败");
// SendMsgUtil.sendJsonMessage(response, baseResult);
// return false;
// }else {
// if(token.equals(tokenMd5)) {
// setHeader(token, userId);
// }else {
// resultCodeAndMsg(ResultCode.ERROR, "令牌验证失败");
// SendMsgUtil.sendJsonMessage(response, baseResult);
// return false;
// }
// }
setHeader("", userId);
LoginUser.setUser(user);
}
}
MongoClient mongoClient = SpringUtil.getBean("mongoClient");
MongoTransactionHandle.startTransaction(mongoClient);//开启事务
return true;
}
/**
* 该方法会在控制器方法调用之后,且解析视图之前执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
MongoTransactionHandle.commitTransaction();//提交事务
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
}
}
monogDB 事务处理器 MongoTransactionHandle.java
package com.tree.common.config;
import com.mongodb.MongoClient;
import com.mongodb.MongoException;
import com.mongodb.client.ClientSession;
/**
* @ClassName: MongoTransactionHandle
* @Description: monogDB 事务处理器
* @author chen shihua
* @date 2018年7月10日 下午1:46:00
* @version V1.0
*/
public class MongoTransactionHandle {
private static ThreadLocal clientSessionLocal = new ThreadLocal();
/**
* @Title: startTransaction
* @Description: 开启事务
* @return void 返回类型
* @author chen shihua
* @throws
*/
public static void startTransaction(MongoClient mongoClient) {
ClientSession clientSession = mongoClient.startSession();
while (true) {
try {
clientSession.startTransaction();
break;
} catch (Exception e) {
clientSession.abortTransaction();
}
}
MongoTransactionHandle.setClientSession(clientSession);
System.out.println("======================开启事务=====================");
}
/**
* @Title: commitTransaction
* @Description: 提交事务
* @return void 返回类型
* @author chen shihua
* @throws
*/
public static void commitTransaction() {
ClientSession clientSession = MongoTransactionHandle.getClientSession();
while (true) {
try {
if(clientSession != null) {
clientSession.commitTransaction();
System.out.println("======================提交事务=====================");
}
break;
} catch (MongoException e) {
// can retry commit
if (e.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) {
System.out.println("UnknownTransactionCommitResult, retrying commit operation ...");
continue;
} else {
System.out.println("Exception during commit ...");
throw e;
}
}
}
}
/**
* @Title: abortTransaction
* @Description: 中止事务
* @return void 返回类型
* @author chen shihua
* @throws
*/
public static void abortTransaction() {
ClientSession clientSession = MongoTransactionHandle.getClientSession();
if(clientSession != null) {
clientSession.abortTransaction();
clientSession = null;
MongoTransactionHandle.setClientSession(clientSession);
System.out.println("======================中止事务=====================");
}
// throw new XueShuException("异常");
}
public static ClientSession getClientSession() {
return clientSessionLocal.get();
}
public static void setClientSession(ClientSession clientSession) {
clientSessionLocal.set(clientSession);
}
}
通过切面捕获未处理的异常 进行中止事务 RestServiceExceptionHandler.java
package com.tree.common.aspect;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import com.tree.common.config.MongoTransactionHandle;
/**
* @ClassName: RestServiceExceptionHandler
* @Description: 服务异常处理
* @author chen shihua
* @version V1.0
*/
@Aspect
public class RestServiceExceptionHandler {
/**
* 定义一个切点
* @throws
*/
@Pointcut("execution(* com.tree.modules.*.service..*.*(..))")
public void serviceMethodPointcut() {
}
/**
* 获取程序中未处理的异常
* @param a
* @throws
*/
@AfterThrowing(pointcut = "serviceMethodPointcut()", throwing = "a")
public void handleServiceMethodException(Throwable a) {
//监听到异常,中止事务
MongoTransactionHandle.abortTransaction();
}
}