MongoDB通用持久类MongoBaseDao
MongoTemplate已经给我们提供了丰富的api,为什么还需要通用持久层类?
第一,比较复杂的持久化方法(如:批量更新、批量删除),MongoTemplate没有开箱即用的api,是需要我们自定义的。
第二,定义通用持久层类支持泛型操作,可以简化整个持久层的代码量,对于简单的业务场景来说,下列持久类MongoBaseDao就可以满足要求。
public class MongoBaseDao<T extends Serializable & BaseEntity<ID>, ID> {
private static final String _id = "_id";
private static final String id = "id";
@Autowired
private MongoClient mongoClient;
// 获取MongoDB操作模板对象
public MongoTemplate getTemplate(String database) {
return new MongoTemplate(mongoClient, database);
}
// 批量更新操作(如果id存在执行更新,如果id不存在执行插入)
public void upsert(List<T> list, Class<T> tClass, String database){
BulkOperations operations = getTemplate(database).bulkOps(BulkOperations.BulkMode.UNORDERED, tClass);
operations.upsert(getPairList(list));
operations.execute();
}
private List<Pair<Query, Update>> getPairList(List<T> list) {
List<Pair<Query, Update>> pairList = new LinkedList<>();
for (T t : list) {
Pair<Query, Update> pair = getPair(t);
pairList.add(pair);
}
return pairList;
}
@SneakyThrows
private Pair<Query, Update> getPair(T t) {
Query query = new Query();
Update update = new Update();
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(t);
Document document = objectMapper.readValue(json, Document.class);
// 这里_id和id的使用注意区分,实体类定义的主键为id,Mongodb的主键为_id
query.addCriteria(Criteria.where(_id).is(document.remove(id)));
// 这里更新了除_id之外所有的字段,适合于先查询后更新的应用场景
document.forEach(update::set);
// 如果是未查询、仅更新某些字段,应排除你未设置值(为null)的其他字段,这时候需使用下列forEach函数
/*document.forEach((key, value)->{
if(ObjectUtils.isNotEmpty(value)){
update.set(key, value);
}
});*/
return Pair.of(query, update);
}
// 批量删除,适用于先查询后删除的使用场景
public void remove(List<T> list, Class<T> tClass, String database){
BulkOperations operations = getTemplate(database).bulkOps(BulkOperations.BulkMode.UNORDERED, tClass);
List<Query> queryList = new LinkedList<>();
for (T t : list) {
queryList.add(Query.query(Criteria.where(_id).is(t.getId())));
}
operations.remove(queryList);
operations.execute();
}
// 根据Id单个删除
public void removeById(ID id, Class<T> tClass, String database){
getTemplate(database).remove(Query.query(Criteria.where(_id).is(id)), tClass);
}
// 根据Id批量删除,适用于未查询就删除的使用场景
public void removeById(List<ID> list, Class<T> tClass, String database){
BulkOperations operations = getTemplate(database).bulkOps(BulkOperations.BulkMode.UNORDERED, tClass);
List<Query> queryList = new LinkedList<>();
for (ID id : list) {
queryList.add(Query.query(Criteria.where(_id).is(id)));
}
operations.remove(queryList);
operations.execute();
}
// 根据Id 批量查询
public List<T> findById(List<ID> list, Class<T> tClass, String database){
return getTemplate(database).find(Query.query(Criteria.where(_id).in(list)), tClass);
}
}
以上api是MongoTemplate没有提供的,定义为持久层通用类后,其他持久类只需继承它即可。列如:
@Repository
public class UserDao extends MongoBaseDao<User, String> {
}
你没看错,其他持久类一般不用定义任何方法,因为MongoTemplate提供的api与MongoBaseDao提供的api已基本够用。
如果要使用MongoTemplate原生的api,使用userDao.getTemplate(database).remove(Object)等。
如果要使用MongoBaseDao自定义api,使用userDao.remove(list, Object.class, database)等。
你可能会说,要是我的业务非常复杂,需要一些其他的Dao方法呢?这个时候的确可以在诸如UserDao的持久类中定义一些其他的方法。但个人建议,既然是业务的复杂度导致的问题,新的方法应该定义在Service层而不是Dao层,Dao层只负责执行增删改查,不负责处理复杂业务,持久类可以继续保持空类状态,业务处理逻辑交给业务类。
类似的,实体类也可以定义一个通用接口。你也许已经注意到MongoBaseDao调用了BaseEntity,它用于统一约定实体类主键字段,从而支持泛型对象获取主键值,即支持批量删除方法中调用t.getId()的实现。
如下,通用实体接口BaseEntity,以及它的实现类User。
public interface BaseEntity<ID> {
ID getId();
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable, BaseEntity<String> {
@Id
private String id;
private String name;
private Integer age;
}
最后,给出MongoClient的配置。你肯定已经注意到MongoBaseDao支持database数据库名传参获取对应的MongoTemplate对象。使用下面的配置类,SpringBoot中央配置文件便可以不用配置任何参数,实现数据库切换操作。当然,前提是你的Mongo数据库提前做好了权限设置。最简单的方式便是禁用MongoDB权限认证,具体设置方式因MongoDB安装方式的不同而不同,此处不再赘述。
@Configuration
public class MongoConfig {
@Bean
public MongoClient mongoClient() {
return MongoClients.create("mongodb://localhost:27017");
}
}