MongoDB 封装

本文档介绍了一个使用Java实现的MongoDB客户端工具类MongoCliDrv,支持增删改查操作,包括分页查询和返回Map及Bean对象。该工具类包含了查询条件的逻辑运算符(AND、OR)以及排序功能,适用于MongoDB的基本数据操作。
摘要由CSDN通过智能技术生成

Amazon

序言:

    近来无事,想着不久将来将用到NoSql,于是选择了MongoDB作为研究切入点。网上找了挺多 MongoDB Client 封装,都过于简单并且只能作为学习MongoDB入门例子,于是自己封装了MongoCliDrv作为与MongoDB交互工具类。
    工具类支持基本的增删改查,查询支持返回Map和Bean对象。封装代码有用到自己helios框架的tool包,本文目的在于MongoDB封装思路上,如有需要读者,可以跟我要 helios-tool.jar 包。

代码如下:

/**
 * MongoDB 操作封装,功能接口包括增删改查
 * 查询支持分页和不分页查询,并支持查询返回 Map 和 Bean对象;
 *
 * by @author lms 2021.08.10
 * last update by lms 2021.08.13 增加查询排序功能
 */
package ms.db.mongodb;

import java.security.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;

import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Sorts;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;

import ms.core.support.Page;
import ms.core.tool.JsonTool;
import ms.core.tool.Log4j2Tool;
import ms.core.tool.PropTool;
import ms.core.tool.StrTool;

public class MongoCliDrv {
	//查询条件拼接逻辑 AND-并关系; OR-或关系; MIX-并和或混合(暂不支持)
	public enum SqlLinkType {AND, OR, MIX}
	//eq=>equal(等于操作); lt=>less than(小于);gt=>greater than(大于);let=>less equal than(小于等于);het=>higher equal than(大于等于);neq=>not equal(不等于)
	public enum Sign{eq,lt,gt,let,get,neq};		//操作符

	private static String configFile = "/config/mongo.properties";
	private String host;
	private int port;
	private String dbName;
	private String userName;
	private String password;
	private int maxConnections;
	private int connectionTimeout;

	private MongoClient cli;

	/**
	 * 设置配置文件
	 * @param conf
	 */
	public static void initMongoConfigFile(String conf) {
		if (StrTool.isBlank(conf) || conf.equalsIgnoreCase(configFile))
			return;

		configFile = conf;
	}

	public static MongoCliDrv getInstance() {
		return new MongoCliDrv();
	}

	private boolean readMongoConfig() {
		try {
			PropTool tool = PropTool.getInstance(configFile);
			host = tool.getValue("host", "127.0.0.1");
			port = StrTool.strToInt(tool.getValue("port", "27017"), 27017);
			dbName = tool.getValue("dbName", "");
			userName = tool.getValue("userName", "");
			password = tool.getValue("password", "");
			maxConnections = StrTool.strToInt(tool.getValue("maxConnections", "100"), 100);
			connectionTimeout = StrTool.strToInt(tool.getValue("connectionTimeout", "3000"), 3000);

			return true;
		}catch(Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	MongoCliDrv() {
		if (cli!=null) return;

		//读取mongo客户端连接配置
		if (!readMongoConfig()) {
			Log4j2Tool.error(MongoCliDrv.class, "MongoDB连接配置文件未指定或不存在!");
			return;
		}

		try {
			if (!StrTool.isBlank(userName)) {
				ServerAddress addr = new ServerAddress(host, port);
				//通过连接认证获取MongoDB连接
				MongoCredential credential = MongoCredential.createScramSha1Credential(userName, dbName, password.toCharArray());
				MongoClientOptions options = MongoClientOptions.builder()
						.connectionsPerHost(maxConnections)
						.connectTimeout(connectionTimeout)
						.cursorFinalizerEnabled(true)				//是否启用游标的 finalize 方法,用于清理客户端未关闭的 DBCursor 实例
						.build();
				cli = new MongoClient(addr, credential, options);
			} else {
				cli = new MongoClient(host, port);
			} 
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
		}
	}

	public void close() {
		try {
			if (cli!=null) cli.close();
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
		}
		cli = null;
	}
	
	private MongoCollection<Document> getCollectionObject(String collectionName) {
		try {
			MongoDatabase db = cli.getDatabase(dbName);
			MongoCollection<Document> collection = db.getCollection(collectionName);
			return collection;
		} catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return null;
		}
	}

	/**
	 * 写入单条数据
	 * @param collectionName 集合名称
	 * @param value 写入数据对象,可以是json字符串、JavaBean、Map
	 * @return
	 */
	public int insertDocument(String collectionName, Object value) {
		try {
			MongoCollection<Document> collection = getCollectionObject(collectionName);
			Map<String, Object> map = JsonTool.objectToMap(value, false);
			Document doc = new Document(map);
			collection.insertOne(doc);
			return 1;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return -1;
		}
	}

	/**
	 * 插入 List<Map>
	 * @param collectionName
	 * @param values
	 * @return
	 */
	public int insertListMapToDocuments(String collectionName, List<Map<String, Object>> values) {
		try {
			MongoCollection<Document> collection = getCollectionObject(collectionName);
			
			int size = values.size();
			List<Document> list = new ArrayList<>();
			for(int i=0; i<size; i++) {
				list.add(new Document(values.get(i)));
			}
			collection.insertMany(list);
			return values.size();
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return -1;
		}
	}

	/**
	 * 批量写入数据
	 * @param collectionName
	 * @param values
	 * @return
	 */
	public <T> int insertListBeanToDocuments(String collectionName, List<T> values) {
		try {
			MongoCollection<Document> collection = getCollectionObject(collectionName);
			
			int size = values.size();
			List<Document> list = new ArrayList<>();
			for(int i=0; i<size; i++) {
				list.add(new Document(JsonTool.objectToMap(values.get(i), true)));
			}
			collection.insertMany(list);
			return values.size();
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return -1;
		}
	}
	
	/**
	 * 条件参数转  Bson 过滤对象
	 * @param params
	 * @param linkType
	 * @return
	 */
	private Bson toFilter(List<FieldParam> params, SqlLinkType linkType){
		if (params==null || params.size()==0)
			return null;

		List<Bson> filters = new ArrayList<>();
		for(int i=0; i<params.size(); i++) {
			FieldParam p = params.get(i);
			switch (p.getSign()) {
				case eq:	//等于
					filters.add(Filters.eq(p.getKey(), p.getValue()));
					break;
				case lt:	//小于
					filters.add(Filters.lt(p.getKey(), p.getValue()));
					break;
				case gt:	//大于
					filters.add(Filters.gt(p.getKey(), p.getValue()));
					break;
				case let:	//小等于
					filters.add(Filters.lte(p.getKey(), p.getValue()));
					break;
				case get:	//大等于
					filters.add(Filters.gte(p.getKey(), p.getValue()));
					break;
				case neq:	//不等于
					filters.add(Filters.ne(p.getKey(), p.getValue()));
					break;
				default: break;
			}
		}
		
		//条件组合
		switch (linkType) {
			case AND: return Filters.and(filters);
			case  OR: return Filters.or(filters);
			default: return null;
		}
	}

	/**
	 * 条件 Map 参数转 Bson 并过滤对象
	 * @param params
	 * @param linkType
	 * @return
	 */
	private Bson toFilter(Map<String, Object> cdt){
		if (cdt==null)
			return null;

		List<Bson> filters = new ArrayList<>();
		for(Map.Entry<String, Object> entry: cdt.entrySet()) {
			filters.add(Filters.eq(entry.getKey(), entry.getValue()));
		}
		return Filters.and(filters);
	}

	/**
	 * 排序参数转对象
	 * @param list
	 * @return
	 */
	private Bson toOrderBy(List<Sort> list) {
		if (list==null || list.size()==0) return null;
		
		List<Bson> tmp = new ArrayList<>();
		for(int i=0; i<list.size(); i++) {
			Sort e = list.get(i);
			if (e.getSortType()==Sort.SortType.ASC)
				tmp.add(Sorts.ascending(e.getFieldName()));
			else
				tmp.add(Sorts.descending(e.getFieldName()));
		}
		
		return Sorts.orderBy(tmp);
	}
			
	/**
	 * 删除文档数据接口
	 * @param collectionName 集合名称
	 * @param delFilter 删除条件
	 * @return
	 */
	public long deleteDocument(String collectionName, Bson delFilter) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);
			DeleteResult ret = collection.deleteMany(delFilter);
			return ret.getDeletedCount();
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return -1;
		}	
	}

	/**
	 * 根据条件(key=value)并关系批量删除
	 * @param collectionName
	 * @param cdt
	 * @return
	 */
	public long deleteDocument(String collectionName, Map<String, Object> cdt) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);
			
			Bson filter = this.toFilter(cdt);
			DeleteResult ret = collection.deleteMany(filter);
			return ret.getDeletedCount();
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return -1;
		}
	}

	/**
	 * 根据条件"and"或者"or"关系批量删除
	 * @param collectionName 集合名称
	 * @param param 删除条件
	 * @return
	 */
	public long deleteDocument(DeleteParam param) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(param.getCollection());
			Bson filter = toFilter(param.getFields(), param.getLinkType());
			DeleteResult ret = collection.deleteMany(filter);
			return ret.getDeletedCount();
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return -1;
		}
	}
	
	/**
	 * 删除集合
	 * @param collectionName
	 * @return
	 */
	public boolean dropCollection(String collectionName) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);
			collection.drop();
			return true;			
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return false;			
		}
	}
	
	/**
	 * 按指定条件更新指定结果值
	 * @param collectionName 集合名称
	 * @param cdt 更新条件
	 * @param value 更新值
	 * @return
	 */
	public long updateDocument(String collectionName, Map<String, Object> cdt, Map<String, Object> value) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);
			
			Bson filter = this.toFilter(cdt);
			Document doc = new Document(value);
			UpdateResult ret = collection.updateMany(filter, doc);
			return ret.getModifiedCount();			
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return -1;			
		}
	}

	//
	     查询功能接口      ///
	//
	
	/**
	 * 对象转换工具: Document 转 map
	 * @param doc
	 * @return
	 */
	public static Map<String, Object> toMap(Document doc){
		if (doc==null) return null;

		Map<String, Object> ret = new HashMap<>();
		
		for(Map.Entry<String, Object> item: doc.entrySet()) {
			if (item.getValue() instanceof ObjectId)
				ret.put(item.getKey(), ((ObjectId)item.getValue()).toString());
			else if (item.getValue() instanceof Timestamp) 
				ret.put(item.getKey(), ((Timestamp)item.getValue()).getTimestamp().getTime());
			else
				ret.put(item.getKey(), item.getValue());
		}
		return ret;
	}
	
	/**
	 * 对象转换工具: map转bean对象
	 * @param map
	 * @param cls
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T toBean(Map<String, Object> map, Class<?> cls){
		return (T) JsonTool.objectToBean(map, cls);
	}

	/**
	 * 对象转换工具: Document转bean对象
	 * @param doc
	 * @param cls
	 * @return
	 */
	public static <T> T toBean(Document doc, Class<?> cls){
		return toBean(toMap(doc), cls);
	}

	/**
	 * 根据查询条件查询数据(原生态查询方式)
	 * @param collectionName 集合名称
	 * @param filter 过滤条件对象
	 * @param sort 排序对象
	 * @param page 查询页码(<=0表示不分页)
	 * @param pagesize 页大小(<=0表示不分页)
	 * @return
	 */
	public Page<Map<String, Object>> queryDocument(String collectionName, Bson filter, Bson sort, int page, int pagesize){
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);

			Page<Map<String, Object>> ret = new Page<>();
			ret.setPageInfo(page, pagesize, 0, 0, null);

			long rows = 0;
			int pages = 1;
			FindIterable<Document> docs = null;
			//查询
			if (filter!=null) {
				rows = collection.countDocuments(filter);
				docs = collection.find(filter);
			}
			else {
				rows = collection.countDocuments();
				docs = collection.find();
			}
			if (docs==null) return ret;

			if (sort!=null) docs = docs.sort(sort);		
			//处理分页
			if (page>0 && pagesize>0) {
				pages = Math.round((rows+pagesize-1)/pagesize);
				docs = docs.skip((page-1)*pagesize).limit(pagesize);
				
				if (page>pages) return ret;
			}

			//取出数据
			List<Map<String, Object>> data = new ArrayList<>();
			MongoCursor<Document> cursor = docs.iterator();
			while (cursor.hasNext()) {
				Map<String, Object> item = toMap(cursor.next());
				data.add(item);
			}
			ret.setPageInfo(page, pagesize, pages, (int)rows, data);
			return ret;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return null;
		}
	}

	/**
	 * 根据查询条件查询数据(原生态查询方式)
	 * @param collectionName 集合名称
	 * @param filter 过滤条件对象
	 * @param sort 排序对象
	 * @param cls 转换目标类
	 * @param page 查询页
	 * @param pagesize 页大小
	 * @return
	 */
	public <T>Page<T> queryDocument(String collectionName, Bson filter, Bson sort, Class<?> cls, int page, int pagesize){
		Page<Map<String, Object>> tmp = queryDocument(collectionName, filter, sort, page, pagesize);
		if (tmp==null) return null;

		Page<T> ret = new Page<>();
		ret.setPageInfo(tmp.getPage(), tmp.getPages(), tmp.getPages(), tmp.getRows(), null);
		if (tmp.getData()==null || tmp.getData().size()==0) return ret;

		List<T> data = new ArrayList<>();
		try {
			int size = tmp.getData().size();
			for(int i=0; i<size; i++) {
				data.add(toBean(tmp.getData().get(i),cls));
			}
			ret.setData(data);
			return ret;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return null;
		}
	}

	/**
	 * 多条件 "and" 或 "or" 查询
	 * @param <T>
	 * @param param
	 * @param cls
	 * @return
	 */
	public Page<Map<String, Object>> queryDocument(QueryParam param){
		try {
			Bson filter = toFilter(param.getFields(), param.getLinkType());
			Bson orderBy = toOrderBy(param.getSorts());
			return queryDocument(param.getCollection(), filter, orderBy, param.getPage(), param.getPagesize());
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return null;
		}
	}

	/**
	 * 多条件 "and" 或 "or" 查询
	 * @param param
	 * @param cls
	 * @return
	 */
	public <T>Page<T> queryDocument(QueryParam param, Class<?> cls){
		Page<Map<String, Object>> tmp = queryDocument(param);
		if (tmp==null) return null;

		Page<T> ret = new Page<>();
		ret.setPageInfo(tmp.getPage(), tmp.getPages(), tmp.getPages(), tmp.getRows(), null);
		if (tmp.getData()==null || tmp.getData().size()==0) return ret;
		
		try {
			List<T> data = new ArrayList<>();
			int size = tmp.getData().size();
			for(int i=0; i<size; i++) {
				data.add(toBean(tmp.getData().get(i),cls));
			}
			ret.setData(data);
			return ret;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return null;
		}
	}

	/**
	 * 根据(key=value)多条件并关系查询
	 * @param param
	 * @param cls
	 * @return
	 */
	public Page<Map<String, Object>> queryDocument(String collectionName, Map<String, Object> cdt){
		QueryParam param = new QueryParam(collectionName);
		
		List<FieldParam> list = new ArrayList<>();
		for(Map.Entry<String, Object> entry: cdt.entrySet()) {
			FieldParam p = new FieldParam(entry.getKey(), entry.getValue(), Sign.eq);
			list.add(p);
		}
		param.setFields(list);
		//不分页
		param.setPage(-1);
		param.setPagesize(0);
		//条件并关系
		param.setLinkType(SqlLinkType.AND);

		return queryDocument(param);
	}

	/**
	 * 根据(key=value)多条件并关系查询
	 * @param param
	 * @param cls
	 * @return
	 */
	public <T>Page<T> queryDocument(String collectionName, Map<String, Object> cdt, Class<?> cls){
		Page<Map<String, Object>> tmp = queryDocument(collectionName, cdt);
		if (tmp==null) return null;
		
		Page<T> ret = new Page<>();
		ret.setPageInfo(tmp.getPage(), tmp.getPageSize(), tmp.getPages(), tmp.getRows(), null);
		if (tmp.getData()==null || tmp.getData().size()==0) return ret;
		
		try {
			List<T> data = new ArrayList<>();
			int size = tmp.getData().size();
			for(int i=0; i<size; i++) {
				data.add(toBean(tmp.getData().get(i),cls));
			}
			ret.setData(data);
			return ret;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return null;
		}
	}

	/**
	 * Mongo SQL 条件
	 * @author lms
	 */
	public static class QueryParam{
		private String collection;			//集合名称
		private int page;					//查询起始页
		private int pagesize;				//页大小
		private List<FieldParam> fields;	//查询参数
		private SqlLinkType linkType;		//条件拼接符号
		private List<Sort> sorts;			//排序参数

		QueryParam(String collection){ this.collection = collection; }
		
		public String getCollection() { return collection; }
		public void setCollection(String collection) { this.collection = collection; }

		public int getPage() { return page; }
		public void setPage(int page) { this.page = page; }

		public int getPagesize() { return pagesize; }
		public void setPagesize(int pagesize) { this.pagesize = pagesize; }

		public List<FieldParam> getFields() { return fields; }
		public void setFields(List<FieldParam> fields) { this.fields = fields; }
		public QueryParam addFieldParam(String key, Object value, Sign sign) {
			if (fields==null) fields = new ArrayList<>();
			FieldParam p = new FieldParam(key, value, sign);
			fields.add(p);
			return this;
		}

		public SqlLinkType getLinkType() { return linkType; }
		public void setLinkType(SqlLinkType linkType) { this.linkType = linkType; }

		public List<Sort> getSorts() { return sorts; }
		public void setSorts(List<Sort> sorts) { this.sorts = sorts; }
		public QueryParam addSort(String field, Sort.SortType sort) {
			if (sorts==null) sorts = new ArrayList<>();
			
			sorts.add(new Sort(field, sort));
			return this;
		}
	}

	//删除数据参数对象
	public static class DeleteParam{
		private String collection;
		private List<FieldParam> fields;
		private SqlLinkType linkType;

		public String getCollection() { return collection; }
		public void setCollection(String collection) { this.collection = collection; }
		
		public List<FieldParam> getFields() { return fields; }
		public void setFields(List<FieldParam> fields) { this.fields = fields; }
		public DeleteParam addParam(String key, Object value, Sign sign) {
			if (fields==null) fields = new ArrayList<>();
			FieldParam p = new FieldParam(key, value, sign);
			fields.add(p);
			return this;
		}
		
		public SqlLinkType getLinkType() { return linkType; }
		public void setLinkType(SqlLinkType linkType) { this.linkType = linkType; }
	}

	//单字段查询条件
	public static class FieldParam{
		private String key;							//key名称
		private Object value;						//key值
		private Sign sign;							//操作符

		FieldParam(String key, Object value, Sign sign){
			this.key = key;
			this.value = value;
			this.sign = sign;
		}

		public String getKey() { return key; }
		public void setKey(String key) { this.key = key; }

		public Object getValue() { return value; }
		public void setValue(Object value) { this.value = value; }

		public Sign getSign() { return sign; }
		public void setSign(Sign sign) { this.sign = sign; }
	}
	
	//排序
	public static class Sort{
		public enum SortType{ASC, DESC};

		Sort(String fieldName, SortType sortType){
			this.fieldName = fieldName;
			this.sortType = sortType;
		}

		private String fieldName;
		private SortType sortType;
		
		public String getFieldName() { return fieldName; }
		public void setFieldName(String fieldName) { this.fieldName = fieldName; }
		
		public SortType getSortType() { return sortType; }
		public void setSortType(SortType sortType) { this.sortType = sortType; }
	}

	public static void main(String args[]) {
		MongoCliDrv.initMongoConfigFile("/config/mongo.properties");
		MongoCliDrv mongo = MongoCliDrv.getInstance();
		//Map<String, Object> value = new HashMap<>();
		//value.put("recordTime", new Date());
		//value.put("name", "linkin");
		//value.put("age", 10);
		//mongo.insertDocument("test", value);

		QueryParam param = new QueryParam("test");
		param.setPage(1);
		param.setPagesize(5);
		param.addSort("age", Sort.SortType.DESC);
		
		param.addFieldParam("age", 15, Sign.let);
		param.addFieldParam("age", 1, Sign.get);
		param.setLinkType(SqlLinkType.AND);
		System.out.println(JsonTool.objectToJson(mongo.queryDocument(param, Person.class)));

		mongo.close();
	}
}

MongoDB连接配置文件:

#mongo服务地址
host=127.0.0.1
#mongo服务端口,默认27017
port=27017
#database
dbName=MyMongo
#数据库帐户,如果为空,则跳过用户验证
userName=
#数据库密码
password=
#每个客户端最大连接数,默认为100
maxConnections=200
#连接超时时间,推荐>3000毫秒
connectionTimeout=5000
#线程队列数,如果连接线程排满了队列就会抛出"Out of semaphores to get db"错误
maxBlockThreads=500

Maven pom.xml 配置文件:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>helios.db</groupId>
  <artifactId>helios-mongo</artifactId>
  <version>helios-mongodb-1.0.0</version>
  <packaging>jar</packaging>

  <name>helios-mongo</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <helios-tool.version>1.0.0</helios-tool.version>
  </properties>

  <dependencies>
 	<!-- 引入框架工具包 -->
    <dependency>
    	<groupId>helios.core</groupId>
        <artifactId>helios-tool</artifactId>
        <version>${helios-tool.version}</version>
    </dependency>

	<!-- https://mvnrepository.com/artifact/org.mongodb/mongo-java-driver -->
	<dependency>
    	<groupId>org.mongodb</groupId>
    	<artifactId>mongo-java-driver</artifactId>
    	<version>3.12.10</version>
	</dependency>
  </dependencies>
</project>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值