The REST API can be easily implemented as follows: The MongoDb seamless integration can be implemented as follows: Spring Boot = Spring Boot = Spring Boot = Spring Boot Use JWT and Spring Security to secure Spring Boot for the REST API back end (5) — Cross domain, custom query, and paging back end (6) — hot load, container, and multi-project

The example we did in the last section was a little too simple, and the usual background involves doing some database operations and then providing the processed data to the client in an exposed API. So what we’re going to do in this section is integrate MongoDB (www.mongodb.com).

What is MongoDB?

MongoDB is a NoSQL database, is a branch of NoSQL: document database. It is very different from traditional relational databases such as Oracle, SQLServer and MySQL. The traditional relational database (RDBMS) has been synonymous with database for more than 20 years. Relational databases are easier to understand for most developers, as table structures and SQL, a standardized query language, are already skills of many developers. So why this nonsense NoSQL, and it seems that NoSQL databases are rapidly gaining market share.

What are the application scenarios of NoSQL?

Let’s say we’re building a forum where users can post (text, video, audio, images, etc.). So we can draw a table relational structure as shown below.

Brief ER diagram of the forum

In this case, let’s think about how the structure of such a post is displayed on the page. If we want to display the text of the post, as well as the associated pictures, audio, video, user comments, likes and user information, we need to associate eight tables to get the data we want. If we have a list of posts that are loaded dynamically as the user scrolls, we need to listen for new content to be generated. We need too many of these complex queries for such a task.

NoSQL solves this problem by simply abandoning the traditional table structure. If you have a structure relationship, I will store and transfer such data to you directly, as shown below.

{
    "id":"5894a12f-dae1-5ab0-5761-1371ba4f703e"."title":"Spring 2017 direction"."date":"2017-01-21"."body":This article explores how to integrate NoSQL with Spring Boot.."createdBy":User,
    "images": ["http://dev.local/myfirstimage.png"."http://dev.local/mysecondimage.png"]."videos":[
        {"url":"http://dev.local/myfirstvideo.mp4"."title":"The first video"},
        {"url":"http://dev.local/mysecondvideo.mp4"."title":"The second video"}]."audios":[
        {"url":"http://dev.local/myfirstaudio.mp3"."title":"The first audio"},
        {"url":"http://dev.local/mysecondaudio.mp3"."title":"The second audio"}}]Copy the code

NoSQL generally does not have the concept of Schema, which also gives development greater freedom. Because in a relational database, once the Schema is determined, it can be cumbersome to maintain the Schema. On the other hand, Schema is necessary to maintain data integrity.

In general, if you are working on a Web, Internet of Things, etc type of project, you should consider using NoSQL. If you are dealing with an environment (such as a financial system) that has strict requirements on data integrity, transaction processing, etc., you should consider a relational database.

Integrate MongoDB in Spring

Integrating MongoDB in the project we just completed was frighteningly easy, with just three steps:

  1. inbuild.gradleChanges in thecompile('org.springframework.boot:spring-boot-starter-web')compile("org.springframework.boot:spring-boot-starter-data-rest")
  2. inTodo.javaTo give inprivate String id;I’m going to start with a metadata modifier@IdSo that Spring knows that this Id is the Id in the database
  3. Create a new one like thisTodoRepository.java
package dev.local.todo;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@RepositoryRestResource(collectionResourceRel = "todo", path = "todo")
public interface TodoRepository extends MongoRepository<Todo.String>{}Copy the code

We don’t even need controllers at this point, so comment out the code in todoController.java for now. Then we launch the application./gradlew bootRun. Visit http://localhost:8080/todo we will get the following results.

{
    _embedded: {
        todo: [ ]
    },
    _links: {
        self: {
            href: "http://localhost:8080/todo"
        },
        profile: {
            href: "http://localhost:8080/profile/todo"
        }
    },
    page: {
        size: 20,
        totalElements: 0,
        totalPages: 0,
        number: 0
    }
}Copy the code

Not only does the dataset return todo: [], but it also comes with a Links object and a Page object. If you understand the concept of Hypermedia, you will see that this is the data returned by the Hypermedia REST API.

MongoRepository

: MongoRepository

: MongoRepository

: MongoRepository
,>
,>
,>
,>

Hypermedia REST

Just a few words about what Hypermedia is. Simply put, it lets the client know exactly what it can do without relying on the server to tell you what to do. The principle is simple by returning results that include not only the data itself, but also links to related resources. With the example above (although this default state generated things is not very representative) : one of the links profiles, we look at what is the profile of the results of the link http://localhost:8080/profile/todo:

{
  "alps" : {
    "version" : "1.0"."descriptors": [{"id" : "todo-representation"."href" : "http://localhost:8080/profile/todo"."descriptors": [{"name" : "desc"."type" : "SEMANTIC"
              }, 
              {
                "name" : "completed"."type" : "SEMANTIC"}]}, {"id" : "create-todo"."name" : "todo"."type" : "UNSAFE"."rt" : "#todo-representation"
        }, 
        {
          "id" : "get-todo"."name" : "todo"."type" : "SAFE"."rt" : "#todo-representation"."descriptors": [{"name" : "page"."doc" : {
                  "value" : "The page to return."."format" : "TEXT"
                },
                "type" : "SEMANTIC"
              }, 
              {
                "name" : "size"."doc" : {
                  "value" : "The size of the page to return."."format" : "TEXT"
                },
                "type" : "SEMANTIC"
              }, 
              {
                "name" : "sort"."doc" : {
                  "value" : "The sorting criteria to use to calculate the content of the page."."format" : "TEXT"
                },
                "type" : "SEMANTIC"}]}, {"id" : "patch-todo"."name" : "todo"."type" : "UNSAFE"."rt" : "#todo-representation"
        }, 
        {
          "id" : "update-todo"."name" : "todo"."type" : "IDEMPOTENT"."rt" : "#todo-representation"
        }, 
        {
          "id" : "delete-todo"."name" : "todo"."type" : "IDEMPOTENT"."rt" : "#todo-representation"
        }, 
        {
          "id" : "get-todo"."name" : "todo"."type" : "SAFE"."rt" : "#todo-representation"}}}]Copy the code

This object, though not fully understood, is a metadata description of the TODO REST API, telling us what operations are defined in the API, what parameters are accepted, etc. We can see that the TOdo API has the corresponding functions such as add, delete, modify and check.

In fact, Spring uses an API called ALPS (ALPS. IO /spec/index… Data formats that specifically describe application semantics. This describes a get method whose type is SAFE, indicating that the operation has no effect on the system state (because it is only a query) and that the result returned by this operation is defined in the todo-representation format. todo-representation

{
  "id" : "get-todo"."name" : "todo"."type" : "SAFE"."rt" : "#todo-representation"
}Copy the code

Still don’t understand? That’s ok, let’s do another experiment and launch PostMan (if you don’t know, you can search and download it from the Chrome App Store). We use Postman to construct a POST request:

Build a POST request with Postman to add a Todo

The result is as follows, We can see the return of the links included just add Todo link http://localhost:8080/todo/588a01abc5d0e23873d4c1b8 (588 a01abc5d0e23873d4c1b8 Is the Id automatically generated by the database for the Todo, so that the client can easily know the API link to the newly generated Todo.

Returns Json data after Todo is added

To take a more realistic example, when we develop a “my” page, we usually get some information about me, because there will also be links to more specific information on the page. If the client gets links to these details at the same time as the summary, the development of the client is easier and more flexible.

In fact, the description also tells us some paging information, such as 20 records per page (size: 20), totalPages (totalPages: 1), totalElements (totalElements: 1), current page (number: 0). Of course, you can also specify page, size, or sort parameters when sending the API request. Such as http://localhost:8080/todos? Page =0&size=10 specifies 10 entries per page. The current page is the first page (starting from 0).

Behind the magic

The simple creation of a database-backed REST API may seem like magic, but it doesn’t always feel like magic until we know what’s behind it. First, let’s review the TodoRepository code:

@RepositoryRestResource(collectionResourceRel = "todo", path = "todo")
public interface TodoRepository extends MongoRepository<Todo.String>{}Copy the code

Spring is one of the first IoC (inversion of Control, or DI) frameworks, so dependency injection is one of its best practices. Here we write an Interface, and you can guess that Spring must have an implementation of this Interface injected into it at runtime. If we go to spring – data – see the mongo’s source code will know what is going on, here is only a small example, you can go to have a look at SimpleMongoRepository. Java (source links), due to the source code is too long, I only intercept part of:

public class SimpleMongoRepository<T.ID extends Serializable> implements MongoRepository<T.ID> {

    private final MongoOperations mongoOperations;
    private final MongoEntityInformation<T, ID> entityInformation;

    /**
     * Creates a new {@link SimpleMongoRepository} for the given {@link MongoEntityInformation} and {@link MongoTemplate}.
     * 
     * @param metadata must not be {@literal null}.
     * @param mongoOperations must not be {@literal null}.
     */
    public SimpleMongoRepository(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {

        Assert.notNull(mongoOperations);
        Assert.notNull(metadata);

        this.entityInformation = metadata;
        this.mongoOperations = mongoOperations;
    }

    /* * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object) */
    public <S extends T> S save(S entity) {

        Assert.notNull(entity, "Entity must not be null!");

        if (entityInformation.isNew(entity)) {
            mongoOperations.insert(entity, entityInformation.getCollectionName());
        } else {
            mongoOperations.save(entity, entityInformation.getCollectionName());
        }

        returnentity; }...public T findOne(ID id) {
        Assert.notNull(id, "The given id must not be null!");
        return mongoOperations.findById(id, entityInformation.getJavaType(), entityInformation.getCollectionName());
    }

    private Query getIdQuery(Object id) {
        return new Query(getIdCriteria(id));
    }

    private Criteria getIdCriteria(Object id) {
        returnwhere(entityInformation.getIdAttribute()).is(id); }... }Copy the code

That is, at run time Spring injects this class or any other implementation of a specific interface into the application. This class has operations that support various databases. I know that this step is ok, so if you’re interested, you can go ahead and study it.

We don’t want to go into specific classes, but we should learn more about MongoRepository. This interface inherits the PagingAndSortingRepository (defines the sorting and paging) and QueryByExampleExecutor. And PagingAndSortingRepository inherited CrudRepository (defines the add and delete etc.).

RepositoryRestResource(collectionResourceRel = “todo”, path = “todo”) It maps directly to the collection in MongoDB (toDO in this case) to a REST URI (ToDO). So we did the API without even writing Controller, and it was Hypermedia REST.

In fact, this second magic is only needed if you need to change the mapping path. In this case, if we hadn’t RepositoryRestResource @repositoryRestResource, we could have made the API the same way, but the path would have been todoes by default. You could try removing the metadata modifier and restarting the service. Go to http://localhost:8080/todoes and have a look.

While we’re at it, let me mention some of the conventions of REST. In general, if we define a domain objects (such as the us Todo), then this set of objects (such as a Todo list) can use this object URL naming way of plural defines its resources, that is just our visit http://localhost:8080/todoes, The operation to add an object is the same URL, but the Request method is POST.

The object of a specified (for example specifies an ID Todo) can use todoes / : ID to access, such as in this case, http://localhost:8080/todoes/588a01abc5d0e23873d4c1b8. The same URL is used to modify and DELETE the object, except that the HTTP Request methods are PUT (or PATCH) and DELETE.

The default name todoes is based on The English grammar. Generally speaking, s can be added to plural, but this todo ends with consonant + O, so es is added. Todo is not really a word, so I think a more logical name would be todos. So we’re going todo @RepositoryRestResource(collectionResourceRel = “todos”, path = “todos”)

Nothing wins, nothing wins

All of these are out of the box methods. You might think, these things look cool, but they don’t really work. In the actual development process, we’re not going to use this simple add, delete, change and check. That’s a good point. Let’s try non-default methods. So let’s add a requirement that we can search for items that match by querying the keywords in Todo’s description.

Obviously this query is not the default operation, so how do you implement this requirement in Spring Boot? It’s as simple as adding a method to TodoRepository:

.public interface TodoRepository extends MongoRepository<Todo.String>{
    List<Todo> findByDescLike(@Param("desc") String desc);
}Copy the code

That’s amazing. Is that all it takes? Don’t believe it can start the service, enter http://localhost:8080/todos/search/findByDescLike? in your browser Desc =swim to see the result. Yes, we did not even write the realization of this method has completed the requirements (digression, in fact, http://localhost:8080/todos? Desc =swim this URL also works.

You said there must be a ghost here, and I agree. So let’s try calling this method findDescLike, and it doesn’t work. Why is that? The magic therapy is still the Convention over Configuration we mentioned in the first article. To achieve the magic effect, we must follow the formula of Spring. The formula is that method naming is tricky: Spring provides a mechanism for building queries using naming conventions. This mechanism filters the method name first for keywords such as find… By, read… By the query… By the count… By and get… By. The system resolves the name into two substatements based on the keyword. The first By is the keyword that distinguishes the two substatements. The substatement before the By is a query substatement (indicating that the object to be queried is returned), followed By a conditional substatement. If it’s just findBy… What is returned is the collection of domain objects (in this case, the collection of TODos) specified when Respository is defined.

Find… By, read… By the query… By, get… What’s the difference between “By”? The answer is… As you can see from the following definitions, these items generate the same query. This allows you to write correctly without having to look up the document, and is also close to the popular natural language description style (similar to various DSLS).

private static final String QUERY_PATTERN = "find|read|get|query|stream";Copy the code

We have just experimented with fuzzy queries, so what if we want Todo precise searches, such as filter out completed or unfinished todos, it is also easy:

  List<Todo> findByCompleted(@Param("completed") boolean completed);Copy the code

How to query nested objects?

And you look at this and you say, well, these are simple types, but what about complex types? Well, let’s add another requirement: now the requirement is that the API be multi-user, and that each user sees Todo as a project they created. Let’s create a new User domain object:

package dev.local.user;

import org.springframework.data.annotation.Id;

public class User {
    @Id private String id;
    private String username;
    private String email;
    // Omit the getter and setter for the property here to save space
}Copy the code

In order to add User data, we need a REST API for User, so add a UserRepository

package dev.local.user;

import org.springframework.data.mongodb.repository.MongoRepository;

public interface UserRepository extends MongoRepository<User.String> {}Copy the code

Then add a User attribute to the Todo domain object.

package dev.local.todo;
// omit the import part
public class Todo {
    // omit other parts
    private User user;

    public User getUser(a) {
        return user;
    }

    public void setUser(User user) {
        this.user = user; }}Copy the code

TodoRepository: TodoRepository: TodoRepository: TodoRepository: TodoRepository: TodoRepository: TodoRepository: TodoRepository: TodoRepository: TodoRepository: TodoRepository: TodoRepository: TodoRepository: TodoRepository

public interface TodoRepository extends MongoRepository<Todo.String>{
    List<Todo> findByUserEmail(@Param("userEmail") String userEmail);
}Copy the code

Now you need to construct some data, which you can do using the Postman tool using the API we just created: we have created two users and some Todo projects that belong to these two users, and some of the projects have the same description. You can experiment, we enter http://localhost:8080/todos/search/findByUserEmail? in your browser UserEmail [email protected], we will see that only the user’s Todo project is returned.

{
  "_embedded" : {
    "todos" : [ {
      "desc" : "go swimming",
      "completed" : false,
      "user" : {
        "username" : "peng",
        "email" : "[email protected]"
      },
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/todos/58908a92c5d0e2524e24545a"
        },
        "todo" : {
          "href" : "http://localhost:8080/todos/58908a92c5d0e2524e24545a"
        }
      }
    }, {
      "desc" : "go for walk",
      "completed" : false,
      "user" : {
        "username" : "peng",
        "email" : "[email protected]"
      },
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/todos/58908aa1c5d0e2524e24545b"
        },
        "todo" : {
          "href" : "http://localhost:8080/todos/58908aa1c5d0e2524e24545b"
        }
      }
    }, {
      "desc" : "have lunch",
      "completed" : false,
      "user" : {
        "username" : "peng",
        "email" : "[email protected]"
      },
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/todos/58908ab6c5d0e2524e24545c"
        },
        "todo" : {
          "href" : "http://localhost:8080/todos/58908ab6c5d0e2524e24545c"
        }
      }
    }, {
      "desc" : "have dinner",
      "completed" : false,
      "user" : {
        "username" : "peng",
        "email" : "[email protected]"
      },
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/todos/58908abdc5d0e2524e24545d"
        },
        "todo" : {
          "href" : "http://localhost:8080/todos/58908abdc5d0e2524e24545d"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/todos/search/[email protected]"
    }
  }
}Copy the code

After seeing the results, let’s analyze how findByUserEmail resolves: First, after By, the parser divides words according to Camel (capitalization of the first letter of each word). So the first word is User, does that property exist in Todo? Yes, but the property is of another object type, so the Email immediately following the word will have to look for the Email property in the User class. You’re smart enough to think, well, what if there’s another property in the Todo class called userEmail? Yes, userEmail will be matched preferentially. use _ to handle the confusion. That is, if our Todo class has both user and userEmail, and we want to specify the email of user, we need to write findByUser_Email.

Another problem, which I’m sure many of you are already thinking about, is that we didn’t use the user ID in this example, which is unscientific. Yes, the reason I didn’t use findByUserId above is to introduce an error prone point, so let’s try changing the TodoRepository method to

public interface TodoRepository extends MongoRepository<Todo.String>{
    List<Todo> findByUserId(@Param("userId") String userId);
}Copy the code

If you open the browser to enter http://localhost:8080/todos/search/findByUserId? UserId = 589089c3C5d0e2524e245458 (please change the Id to your own mongodb userId), you will find that the result is an empty array. The reason is that although the id is identified as String in the class, for a field that is generated and maintained by the database itself, the type in MongoDB is ObjectId. Therefore, the query function defined by our interface should identify this parameter as ObjectId. All we need to do is change the userId type to org.bson.types.ObjectId.

package dev.local.todo;

import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

import java.util.List;

@RepositoryRestResource(collectionResourceRel = "todos", path = "todos")
public interface TodoRepository extends MongoRepository<Todo.String>{
    List<Todo> findByUserId(@Param("userId") ObjectId userId);
}Copy the code

Can we make it more complicated?

Well, by now I estimate there are a large wave of siege lions expressed dissatisfaction, the actual development needs to query more complex than the above, more complex how to do? Again, using examples, we now want to fuzzily search for the keywords described in the specified user’s Todo, returning a set of matches. We only need to change this one line, the naming rule based query criteria can add And, Or, such as the association of multiple criteria keywords.

List<Todo> findByUserIdAndDescLike(@Param("userId") ObjectId userId, @Param("desc") String desc);Copy the code

Of course, there are other operators: Between, LessThan, GreaterThan, Like, IgnoreCase (b ignores case), AllIgnoreCase (ignores case for multiple arguments), OrderBy (bootsort substatement), Asc (ascending, valid only after OrderBy), and Desc (descending, valid only after OrderBy).

All we have talked about is the construction of a query condition substatement. Before By, you can also have the qualifier Distinct (to duplicate a value, for example) for the object being queried. Such as might be the result returned a duplicate records, can use findDistinctTodoByUserIdAndDescLike.

Can I write the query directly? Questions that almost any coder would ask. Of course you can. It’s just as easy to add the metadata modifier @query to the method

public interface TodoRepository extends MongoRepository<Todo.String>{
    @Query("{ 'user._id': ? 0, 'desc': { '$regex': ? 1}}")
    List<Todo> searchTodos(@Param("userId") ObjectId userId, @Param("desc") String desc);
}Copy the code

This way we don’t have to follow the naming convention to name the method, we can use the MongoDB query directly. There are a few things that need to be said about the above example

  1. ? 0? 1Is a placeholder for arguments,? 0Represents the first parameter, which is thetauserId;? 1That’s the second argumentdesc.
  2. useuser._idRather thanuser.idBecause of all the people@IdDecorated properties are converted to Spring Data_id
  3. MongoDB does not have the Like keyword of relational database, so similar functions need to be achieved in the way of regular expression.

In fact, this level of support allows us to write relatively complex queries. But it’s certainly not enough, and for developers, it’s almost impossible to use it without customizing it, because for one reason or another we want to have complete control over our queries or stored procedures. But it’s a little bit more informative, so I’ll talk about it later.

This chapter code: github.com/wpcfan/spri…

The REST API can be easily implemented as follows: The MongoDb seamless integration can be implemented as follows: Use JWT and Spring Security to protect REST apis