JSON API in a Spring Application – Spring应用程序中的JSON API

最后修改: 2015年 10月 13日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we’ll start exploring the JSON-API spec and how that can be integrated into a Spring backed REST API.

在本文中,我们将开始探索JSON-API规范以及如何将其集成到Spring支持的REST API中。

We’ll use the Katharsis implementation of JSON-API in Java – and we’ll set up a Katharsis powered Spring application – so all we need is a Spring application.

我们将使用Java中JSON-API的Katharsis实现–我们将建立一个由Katharsis驱动的Spring应用程序–所以我们所需要的只是一个Spring应用程序。

2. Maven

2.Maven

First, let’s take a look at our maven configuration – we need to add the following dependency into our pom.xml:

首先,让我们看看我们的maven配置–我们需要在pom.xml中添加以下依赖项。

<dependency>
    <groupId>io.katharsis</groupId>
    <artifactId>katharsis-spring</artifactId>
    <version>3.0.2</version>
</dependency>

3. A User Resource

3.一个用户资源

Next, let’s take a look at our User resource:

接下来,让我们看一下我们的用户资源。

@JsonApiResource(type = "users")
public class User {

    @JsonApiId
    private Long id;

    private String name;

    private String email;
}

Note that:

请注意,。

  • @JsonApiResource annotation is used to define our resource User
  • @JsonApiId annotation is used to define the resource identifier

And very briefly – the persistence for this example is going to be a Spring Data repository here:

很简单,这个例子的持久性将是一个Spring Data仓库。

public interface UserRepository extends JpaRepository<User, Long> {}

4. A Resource Repository

4.一个资源库

Next, let’s discuss our resource repository – each resource should have a ResourceRepositoryV2 to publish the API operations available on it:

接下来,让我们讨论一下我们的资源库–每个资源都应该有一个ResourceRepositoryV2来发布其上可用的API操作。

@Component
public class UserResourceRepository implements ResourceRepositoryV2<User, Long> {

    @Autowired
    private UserRepository userRepository;

    @Override
    public User findOne(Long id, QuerySpec querySpec) {
        Optional<User> user = userRepository.findById(id); 
        return user.isPresent()? user.get() : null;
    }

    @Override
    public ResourceList<User> findAll(QuerySpec querySpec) {
        return querySpec.apply(userRepository.findAll());
    }

    @Override
    public ResourceList<User> findAll(Iterable<Long> ids, QuerySpec querySpec) {
        return querySpec.apply(userRepository.findAllById(ids));
    }

    @Override
    public <S extends User> S save(S entity) {
        return userRepository.save(entity);
    }

    @Override
    public void delete(Long id) {
        userRepository.deleteById(id);
    }

    @Override
    public Class<User> getResourceClass() {
        return User.class;
    }

    @Override
    public <S extends User> S create(S entity) {
        return save(entity);
    }
}

A quick note here – this is of course very similar to a Spring controller.

在此简单说明一下–这当然是非常类似于Spring控制器

5. Katharsis Configuration

5.Katharsis配置

As we are using katharsis-spring, all we need to do is to import KatharsisConfigV3 in our Spring Boot Application:

由于我们使用的是katharsis-spring,我们需要做的就是在Spring Boot应用程序中导入KatharsisConfigV3

@Import(KatharsisConfigV3.class)

And configure Katharsis parameters in our application.properties:

并在我们的application.properties中配置Katharsis参数。

katharsis.domainName=http://localhost:8080
katharsis.pathPrefix=/

With that – we can now start consuming the API; for example:

有了这些–我们现在可以开始消费API了;比如说。

  • GET “http://localhost:8080/users“: to get all users.
  • POST “http://localhost:8080/users“: to add new user, and more.

6. Relationships

6.关系

Next, let’s discuss how to handle entities relationships in our JSON API.

接下来,让我们讨论如何在我们的JSON API中处理实体关系。

6.1. Role Resource

6.1.角色资源

First, let’s introduce a new resource – Role:

首先,让我们介绍一种新的资源–Role

@JsonApiResource(type = "roles")
public class Role {

    @JsonApiId
    private Long id;

    private String name;

    @JsonApiRelation
    private Set<User> users;
}

And then set up a many-to-many relation between User and Role:

然后在UserRole之间建立一个多对多的关系。

@JsonApiRelation(serialize=SerializeType.EAGER)
private Set<Role> roles;

6.2. Role Resource Repository

6.2.角色资源库

Very quickly – here is our Role resource repository:

非常快–这里是我们的Role资源库。

@Component
public class RoleResourceRepository implements ResourceRepositoryV2<Role, Long> {

    @Autowired
    private RoleRepository roleRepository;

    @Override
    public Role findOne(Long id, QuerySpec querySpec) {
        Optional<Role> role = roleRepository.findById(id); 
        return role.isPresent()? role.get() : null;
    }

    @Override
    public ResourceList<Role> findAll(QuerySpec querySpec) {
        return querySpec.apply(roleRepository.findAll());
    }

    @Override
    public ResourceList<Role> findAll(Iterable<Long> ids, QuerySpec querySpec) {
        return querySpec.apply(roleRepository.findAllById(ids));
    }

    @Override
    public <S extends Role> S save(S entity) {
        return roleRepository.save(entity);
    }

    @Override
    public void delete(Long id) {
        roleRepository.deleteById(id);
    }

    @Override
    public Class<Role> getResourceClass() {
        return Role.class;
    }

    @Override
    public <S extends Role> S create(S entity) {
        return save(entity);
    }
}

It is important to understand here is that this single resource repo doesn’t handle the relationship aspect – that takes a separate repository.

这里需要了解的是,这个单一的资源库并不处理关系方面的问题–这需要一个单独的资源库。

6.3. Relationship Repository

6.3.关系存储库

In order to handle the many-to-many relationship between UserRole we need to create a new style of repository:

为了处理UserRole之间的多对多关系,我们需要创建一个新式的存储库。

@Component
public class UserToRoleRelationshipRepository implements RelationshipRepositoryV2<User, Long, Role, Long> {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Override
    public void setRelation(User User, Long roleId, String fieldName) {}

    @Override
    public void setRelations(User user, Iterable<Long> roleIds, String fieldName) {
        Set<Role> roles = new HashSet<Role>();
        roles.addAll(roleRepository.findAllById(roleIds));
        user.setRoles(roles);
        userRepository.save(user);
    }

    @Override
    public void addRelations(User user, Iterable<Long> roleIds, String fieldName) {
        Set<Role> roles = user.getRoles();
        roles.addAll(roleRepository.findAllById(roleIds));
        user.setRoles(roles);
        userRepository.save(user);
    }

    @Override
    public void removeRelations(User user, Iterable<Long> roleIds, String fieldName) {
        Set<Role> roles = user.getRoles();
        roles.removeAll(roleRepository.findAllById(roleIds));
        user.setRoles(roles);
        userRepository.save(user);
    }

    @Override
    public Role findOneTarget(Long sourceId, String fieldName, QuerySpec querySpec) {
        return null;
    }

    @Override
    public ResourceList<Role> findManyTargets(Long sourceId, String fieldName, QuerySpec querySpec) {
        final Optional<User> userOptional = userRepository.findById(sourceId);
        User user = userOptional.isPresent() ? userOptional.get() : new User();
        return  querySpec.apply(user.getRoles());
    }

    @Override
    public Class<User> getSourceResourceClass() {
        return User.class;
    }

    @Override
    public Class<Role> getTargetResourceClass() {
        return Role.class;
    }
}

We’re ignoring the singular methods here, in the relationship repository.

我们在这里忽略了关系库中的单数方法。

7. Test

7.测试

Finally, let’s analyze a few requests and really understand what the JSON-API output looks like.

最后,让我们分析几个请求,真正了解JSON-API的输出是什么样子。

We’re going to start retrieving a single User resource (with id = 2):

我们将开始检索一个单一的用户资源(id = 2)。

GET http://localhost:8080/users/2

GET http://localhost:8080/users/2

{
    "data":{
        "type":"users",
        "id":"2",
        "attributes":{
            "email":"tom@test.com",
            "username":"tom"
        },
        "relationships":{
            "roles":{
                "links":{
                    "self":"http://localhost:8080/users/2/relationships/roles",
                    "related":"http://localhost:8080/users/2/roles"
                }
            }
        },
        "links":{
            "self":"http://localhost:8080/users/2"
        }
    },
    "included":[
        {
            "type":"roles",
            "id":"1",
            "attributes":{
                "name":"ROLE_USER"
            },
            "relationships":{
                "users":{
                    "links":{
                        "self":"http://localhost:8080/roles/1/relationships/users",
                        "related":"http://localhost:8080/roles/1/users"
                    }
                }
            },
            "links":{
                "self":"http://localhost:8080/roles/1"
            }
        }
    ]
}

Takeaways:

经验之谈。

  • The main attributes of the Resource are found in data.attributes
  • The main relationships of the Resource are found in data.relationships
  • As we used @JsonApiRelation(serialize=SerializeType.EAGER) for the roles relationship, it is included in the JSON and found in node included

Next – let’s get the collection resource containing the Roles:

接下来–让我们获取包含角色的集合资源。

GET http://localhost:8080/roles

GET http://localhost:8080/roles

{
    "data":[
        {
            "type":"roles",
            "id":"1",
            "attributes":{
                "name":"ROLE_USER"
            },
            "relationships":{
                "users":{
                    "links":{
                        "self":"http://localhost:8080/roles/1/relationships/users",
                        "related":"http://localhost:8080/roles/1/users"
                    }
                }
            },
            "links":{
                "self":"http://localhost:8080/roles/1"
            }
        },
        {
            "type":"roles",
            "id":"2",
            "attributes":{
                "name":"ROLE_ADMIN"
            },
            "relationships":{
                "users":{
                    "links":{
                        "self":"http://localhost:8080/roles/2/relationships/users",
                        "related":"http://localhost:8080/roles/2/users"
                    }
                }
            },
            "links":{
                "self":"http://localhost:8080/roles/2"
            }
        }
    ],
    "included":[

    ]
}

The quick take-away here is that we get all Roles in the system – as an array in the data node

这里的快速收获是,我们得到了系统中的所有角色–作为data节点中的一个数组。

8. Conclusion

8.结论

JSON-API is a fantastic spec – finally adding some structure in the way we use JSON in our APIs and really powering a true Hypermedia API.

JSON-API是一个非常棒的规范–终于为我们在API中使用JSON的方式添加了一些结构,并真正为真正的超媒体API提供了动力。

This piece explored one way to set it up in a Spring app. But regardless of that implementation, the spec itself is – in my view – very very promising work.

这篇稿子探讨了在Spring应用中设置它的一种方法。但不管这种实现方式如何,在我看来,该规范本身是非常非常有前途的工作。

The complete source code for the example is available over on GitHub. It’s a Maven project which can be imported and run as-is.

该例子的完整源代码可在GitHub上获得。它是一个Maven项目,可以按原样导入和运行。