preface

A long time ago, the author used NodeJS to develop a set of API interfaces based on KOA and Mongoose in the background, but the effect was not satisfactory. When there were too many codes, the whole project became bloated, and eggJS was used for reconstruction. However, due to the weak type of javaScript language, Later, an idea of using strongly typed language for reconstruction came into being, referring to the mature scheme of background API and Spring ecology. Finally, Java was selected for reconstruction. The database was based on mongodb, but the spring integration on the Internet basically took mysql as the main relational database. The integration of persistence layer MyBatis-Plus, spring-data-JPA schemes are in the majority, while the integration of Spring Data Mongodb is relatively less, and there is even no code generator for MyBatis-Plus, so I write this article to record my learning notes.

About mongo

Mongodb is a database system built for rapid development of Internet Web applications. Its data model and persistence strategy are designed to build a system with high read/write throughput and high automatic disaster recovery scalability. Spring Data Mongodb is a way to operate Data stores in the Spring Data style. It avoids writing a lot of boilerboard code.

Add the dependent

To integrate Mongodb in Spring Boot, it is very easy to add the Starter package to Mongodb. The code is as follows:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
Copy the code

Then configure Mongodb connection information in application.properties:

Configuration file application.properties

Mysql > select * from mongodb; separated
# spring. The mongo. MongoDatabaseAddress = 10.110.112.165:27092; 10.110.112.166:27092
# mongo domain name
spring.data.mongodb.host=127.0.0.1 
# mongo port
spring.data.mongodb.port=27017
# mongo data
spring.data.mongodb.database=education
# mongo user
spring.mongo.username=admin
# mongo password
spring.mongo.password=123456
#mongo Maximum number of connections
spring.mongo.connectionsPerHost=50

Copy the code

Add data

First create an entity class, we use the student to make the entity class, define the following fields:

@Data
@apiModel (value="Student ", description=" Student ")
@Document(collection = "student")
public class Student  {

    @Id
    private String id;

    

    @Field("name")
    private String name;
    private String birthday;
    private Integer sex;


    private  String contacts; / / the contact
    private  String phone; / / phone

    private  String province;
    private  String city;
    private  String region;
    private  String address;

    private  String teacherId;
    private   List<String> openId = new ArrayList<>();

    private  String desc;
    private  Integer status; // Status: Studying, graduated 1/2

    @apiModelProperty (value = "create time ", example =" 2019-11-10t07:29:32.496 +0000")
    private Date createDate;


    @apimodelproperty (value = "新时间", example =" 2019-11-10t07:29:32.496 +0000")
    private  Date updateDate;
}
Copy the code

The annotations in the entity class are explained below

annotations parsing
@Id The primary key identifier is used to mark the ID field. Entities that do not mark this field will automatically generate the ID field, but we cannot get the ID from the entity. Id You are advised to set the ObjectId type
@Document The value of collection represents the name of the mongodb collection. The default value is student
@DBRef Used to specify cascading relationships with other collections, but note that cascading collections are not created automatically
@Indexed Used to mark the creation of an index for a field
@CompoundIndex Used to create a composite index
@TextIndexed: Used to mark the creation of a full-text index for a field
@Language Specify the documen language
@Transient: Those marked by this annotation will not be entered into the database. As a normal javaBean property only
@Field: This annotation is used to specify the name of a field mapped to the database, so that users can customize the name of the field, which can be inconsistent with the entity class. Another advantage is that you can use abbreviations, such as username, we can configure it as unane or UN. This advantage is to save storage space. Mongodb is stored in the form of key value. Each key is stored repeatedly, and the key occupies a large amount of storage space

Operational data

Mode 1: Sping Data operation

Add the StudentRepository interface to manipulate Mongodb

// Extend the MongoRepository interface to get common data manipulation methods, using derived queries
// Specify the name of the query method directly in the interface. No implementation is required.
public interface StudentRepository extends MongoRepository<Student.String> {
    /** select ** from openID
    Student findFirstByOpenIdIn(String id);


    // Use the @query annotation to Query with Mongodb's JSON Query statement
    @Query("{'name': ? 0} ")
    Student findByLocalName(String name);

    // Use the @aggregation annotation to use Mongodb's Aggregation query statement
    @Aggregation({"{ '$project': { _id : '$status' } }", "{$group: {_id: null, count: {$sum: $_id}}}"})
    Integer findAlls(a);

}
Copy the code

Add the StudentService interface

public interface StudentService  {

   Student selectOne(String id);

}
Copy the code

Add the StudentService interface implementation class StudentServiceImpl

@Service
@Slf4j
public class StudentServiceImpl  implements StudentService {
    @Autowired
    private StudentRepository StudentRepository;
    
    @Override
    public Student selectOne(String name) {
        return this.StudentRepository.findByLocalName(name); }}Copy the code

Add a defining interface for the StudentController

@RestController
@api (description=" Student management ")
@RequestMapping("/student")
public class StudentController extends ApiController {

    @Resource
    StudentService studentService;


    /** * Query a single item by name **@paramThe name name *@returnSingle data */
    @GetMapping("/findName")
    @apiOperation (value = "query student by ID ")
    public R selectOne( @apiParam (name = "name", value = "student name", Required = true) String name) {

        return R.ok(this.studentService.selectOne(name)); }}Copy the code

MongoTemplate approach 2: MongoTemplate

Add data

@Autowired
private MongoTemplate mongoTemplate;

// Add a single
Student stu = new Student();
// Omit data Settings.....
mongoTemplate.save(student);


 // Batch add
List<Student> stus = new ArrayList<>(10);
// Omit data Settings.....
mongoTemplate.insert(stus, Student.class);
Copy the code

Delete operation

// Delete data whose name is Tom
Query query = Query.query(Criteria.where("name").is("Tom"));
mongoTemplate.remove(query, Student.class);
mongoTemplate.remove(query, "student");

// Delete the collection
mongoTemplate.dropCollection(Student.class);
mongoTemplate.dropCollection("student");

// Delete the database
mongoTemplate.getDb().dropDatabase();

// Query the first result that meets the condition and delete the data that meets the condition. Only the first result will be deleted
query = Query.query(Criteria.where("name").is("Tom"));
Student student = mongoTemplate.findAndRemove(query, Student.class);

// Query all results that meet the conditions and delete all data that meet the conditions
query = Query.query(Criteria.where("name").is("Tom"));
List<Student> students = mongoTemplate.findAllAndRemove(query, Student.class);

Copy the code

Modify the operating

// Modify contacts and visitCount in the first data whose name is name
Query query = Query.query(Criteria.where("name").is("Tom"));
Update update = Update.update("contacts"."MongoTemplate").set("age".10);
mongoTemplate.updateFirst(query, update, Student.class);

// Modify all qualified
mongoTemplate.updateMulti(query, update, Student.class);


// Special update, update data with name Tom, create a new data with this condition if there is no data with name Tom
// When there is no matching document, a new document is created based on the matching document and the updated document. If a matching document is found, the normal update is performed.
mongoTemplate.upsert(query, update, Student.class);

// The update condition is unchanged, the update field is changed to a key that does not exist in our collection, use the set method to create a new key if the updated key does not exist
Query query = Query.query(Criteria.where("name").is("Tom"));
Update update = Update.update("contacts"."MongoTemplate").set("money".100);
mongoTemplate.updateMulti(query, update, Student.class);

// Update's Inc method is used to add money by 100
Query query = Query.query(Criteria.where("name").is("Tom"));
update = Update.update("contacts"."MongoTemplate").inc("money".100);
mongoTemplate.updateMulti(query, update, Student.class);

// Update the rename method is used to rename the key
Query query = Query.query(Criteria.where("name").is("Tom"));
update = Update.update("contacts"."MongoTemplate").rename("visitCount"."vc");
mongoTemplate.updateMulti(query, update, Student.class);

// Update's unset method is used to delete keys
Query query = Query.query(Criteria.where("name").is("Tom"));
update = Update.update("contacts"."MongoTemplate").unset("vc");
mongoTemplate.updateMulti(query, update, Student.class);

// Update's pull method is used to remove Java from the Tags array
Query query = Query.query(Criteria.where("name").is("Tom"));
update = Update.update("contacts"."MongoTemplate").pull("tags"."java");
mongoTemplate.updateMulti(query, update, Student.class);


Copy the code

Query operation

Query, whether relational database or noSQL like mongodb, is used more, most of the operations are read operations. MongoTemplate combined with Sort, Criteria, Query and Pageable classes can be used to Query mongodb database flexibly

There are many ways to query mongodb. The following lists some common ones. For example:

  1. = query
  2. Fuzzy query
  3. Greater than or less than range query
  4. In the query
  5. The or query
  6. Query one, query all

Return the List according to the student’s query of all eligible data



Query Query query = Query.query(Criteria.where("name").is("Tom"));
List<Student> students = mongoTemplate.find(query, Student.class);



// paging query
Sort sort = new Sort(Sort.Direction.DESC, "name");
Pageable pageable = PageRequest.of(3.20, sort);
List<Student> StudentPageableList = mongoTemplate.find(Query.query(Criteria.where("name").is(name)).with(pageable), Student.class);



// Return the student object
Query query = Query.query(Criteria.where("name").is("Tom"));
Student student = mongoTemplate.findOne(query, Student.class);


// Use findOne to query the last record based on sort
Student student = mongoTemplate.findOne(Query.query(Criteria.where("name").is(name)).with(sort), Student.class);


// fuzzy query
List<Student> StudentList = mongoTemplate.find(Query.query(Criteria.where("name").is(name).regex(name)).with(sort), Student.class);

/ / the total number of
 long conut = mongoTemplate.count(Query.query(Criteria.where("name").is(name)), Student.class);

// Query all data in the set without conditions
student = mongoTemplate.findAll(Student.class);


// Query the number of conditions

Query query = Query.query(Criteria.where("name").is("Tom"));
long count = mongoTemplate.count(query, Student.class);


// Query by primary key ID

student = mongoTemplate.findById(new ObjectId("87c6e1601e4735b2c3064db7"), Student.class);

/ / in the query

List<String> names = Arrays.asList("jack"."Tom");
query = Query.query(Criteria.where("name").in(names));
students = mongoTemplate.find(query, Student.class);


/ / ne! =) query

query = Query.query(Criteria.where("name").ne("Tom"));
students = mongoTemplate.find(query, Student.class);


// lt(<) queries students whose age is less than 10

query = Query.query(Criteria.where("age").lt(10));
students = mongoTemplate.find(query, Student.class);


// Range query, greater than 5 and less than 10

query = Query.query(Criteria.where("age").gt(5).lt(10));
students = mongoTemplate.find(query, Student.class);


// Fuzzy query, name contains a data

query = Query.query(Criteria.where("name").regex("a"));
students = mongoTemplate.find(query, Student.class);


Openid = 3; openID = 3

query = Query.query(Criteria.where("openid").size(3));
students = mongoTemplate.find(query, Student.class);


Name =Tom or age=10

query = Query.query(Criteria.where("").orOperator(
    Criteria.where("name").is("Tom"),
    Criteria.where("age").is(10)));
students = mongoTemplate.find(query, Student.class);

Copy the code

Aggregation query

// Count the number of students according to the creation time
List<AggregationOperation> operations = new ArrayList();
operations.add(
        Aggregation.project( "name"). // Reserved fields
                andExpression("createDate")
                .dateAsFormattedString("%Y-%m").as("date")
                .andExpression("createDate").extractMonth().as("key")
                .andExpression("id").as("stuId")); operations.add( Aggregation.group("date")
                .first("key").as("key")
                .push("name").as("student")
                .push("stuId").as("ids")
                .count().as("count")); AggregationResults<Map> aggregate =this.mongoTemplate.aggregate(Aggregation.newAggregation(operations), Student.class, Map.class);
Copy the code

To optimize the

You can customize query classes for complex aggregate queries

For example: linked list query, need to define variables

db.getCollection('course').aggregate([{
    $unwind: '$studentIds',
    },
    {
    $lookup: {
        from: 'student',
        let: { stuId: { $toObjectId: '$studentIds' } },
        pipeline: [
        {
            $match: {
            $expr: { $eq: [ '$_id', '$$stuId' ] },
            },
        },
        {
            $project: {
            isSendTemplate: 1,
            openId: 1,
            stu_name: '$name',
            stu_id: '$_id',
            },
        },
        ],
        as: 'student',
    },
    }])
Copy the code

Customize the AggregationOperation class

import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;

public class CustomAggregationOperation implements AggregationOperation {

  private String jsonOperation;

  public CustomAggregationOperation(String jsonOperation) {
    this.jsonOperation = jsonOperation;
  }

  @Override
  public org.bson.Document toDocument(AggregationOperationContext aggregationOperationContext) {
    returnaggregationOperationContext.getMappedObject(org.bson.Document.parse(jsonOperation)); }}Copy the code


@Service
public class LookupAggregation {

  @Autowired
  MongoTemplate mongoTemplate;

  public void LookupAggregationExample(a) {

    AggregationOperation unwind = Aggregation.unwind("studentIds");

    String query1 = "{$lookup: {from: 'student', let: { stuId: { $toObjectId: '$studentIds' } },"
        + "pipeline: [{$match: {$expr: { $eq: [ '$_id', '$$stuId' ] },},}, "
        + "{$project: {isSendTemplate: 1,openId: 1,stu_name: '$name',stu_id: '$_id',},},], "
        + "as: 'student',}, }";

    TypedAggregation<Course> aggregation = Aggregation.newAggregation(
        Course.class,
        unwind,
        newCustomAggregationOperation(query1) ); AggregationResults<Course> results = mongoTemplate.aggregate(aggregation, Course.class); System.out.println(results.getMappedResults()); }}Copy the code

Base Vice IMPL encapsulates the general-purpose CRUD

public class BaseServiceImpl<T extends BaseEntity>  implements BaseService<T > {
    @Autowired
    MongoTemplate mongoTemplate;

    @Autowired
    MongoRepository<T, String> mongoRepository;

    /** * create a Class object to get the generic Class */
    private Class<T> clazz ;

    public Class<T> getClazz(a) {
        if (clazz == null) {
            clazz = ((Class<T>) (((ParameterizedType) (this.getClass().getGenericSuperclass())).getActualTypeArguments()[0]));
        }
        return clazz;
    }

    @Override
    public PageData<T> findQueryPage(Query query) {

        JSONObject queryObj = query.getQuery();


        BasicQuery basicQuery = new BasicQuery(queryObj.toString());


        // Count the total
        long total = this.mongoTemplate.count(basicQuery, getClazz());

        // Add paging
        PageRequest pageable = query.toPageRequest();
        basicQuery.with(pageable);

        List<T> list = this.mongoTemplate.find(basicQuery, getClazz());


        PageData result = new PageData(total, list);

        return result;
    }


    @Override
    public T selectOne(String id) {
        Optional<T> byId = this.mongoRepository.findById(id);

        return byId.orElse(null);
    }

    @Override
    public T insert(T t) {
        t.setCreateDate(new Date());
        t.setUpdateDate(new Date());
        T save = this.mongoRepository.save(t);

        return save;
    }

    @Override
    public T update(T t) {

        Class<? extends BaseEntity> entryClazz = t.getClass();
        Document annotation = entryClazz.getAnnotation(Document.class);
        String collection = annotation.collection();

        org.springframework.data.mongodb.core.query.Query query = new org.springframework.data.mongodb.core.query.Query(Criteria.where("_id").is(t.getId()));

        Update update = new Update();

        Field[] declaredFields = entryClazz.getDeclaredFields();
        Boolean isUpdate = false;
        for (Field targetField : declaredFields) {
            if (Modifier.isStatic(targetField.getModifiers())) { // Skip static methods
                continue;
            }

            targetField.setAccessible(true);


            try {
                Object obj = targetField.get(t);
                String name = targetField.getName();

                if (StringUtils.isEmpty(obj)) {
                    continue;
                }

                if (name.equalsIgnoreCase("createDate") || name.equalsIgnoreCase("updateDate")) {
                    continue;
                }

                // Check if the array is empty
                if (obj.getClass().isArray()) {
                    List list = CollectionUtils.arrayToList(obj);
                    if (list.isEmpty()) {
                        continue; }}else if (obj instanceof  List) { // Check whether the list is empty
                    List list = (ArrayList) obj;
                    if (list.isEmpty()) {
                        continue;
                    }
                }

                isUpdate = true;
                update.set(name, obj);

            } catch(IllegalAccessException e) { e.printStackTrace(); }}if (isUpdate) {
            update.set("updateDate".new Date());
            UpdateResult updateResult = mongoTemplate.updateFirst(query, update, entryClazz);
            long modifiedCount = updateResult.getModifiedCount();
            System.out.println("Update quantity" + modifiedCount);
        }

        return null;
    }

    @Override
    public void removeById(String id) {
        this.mongoRepository.deleteById(id);
    }

    @Override
    public void removeByIds(List<T> all) {
        this.mongoRepository.deleteAll(all); }}Copy the code

Reference documentation

  • Quick Mongodb operations in Spring Boot
  • MongoDB–Spring Data MongoDB detailed operation manual (add, delete, change, check)
  • Mall integrates Mongodb for document manipulation
  • About springdata mongodb aggregation