我将Spring Boot与mysql一起使用来创建Restful API。这是我如何返回json响应的一个示例。
首先我有一个模型:
@Entity
public class Movie extends DateAudit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Date releaseDate;
private Time runtime;
private Float rating;
private String storyline;
private String poster;
private String rated;
@OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, orphanRemoval = true)
private List<MovieMedia> movieMedia = new ArrayList<>();
@OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, orphanRemoval = true)
private List<MovieReview> movieReviews = new ArrayList<>();
@OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, orphanRemoval = true)
private List<MovieCelebrity> movieCelebrities = new ArrayList<>();
// Setters & Getters
}
并对应存储库:
@Repository
public interface MovieRepository extends JpaRepository<Movie, Long> {
}
我还有一个有效载荷类 MovieResponse ,它代表电影而不是电影模型,例如,如果我需要额外的字段或需要返回特定的字段。
public class MovieResponse {
private Long id;
private String name;
private Date releaseDate;
private Time runtime;
private Float rating;
private String storyline;
private String poster;
private String rated;
private List<MovieCelebrityResponse> cast = new ArrayList<>();
private List<MovieCelebrityResponse> writers = new ArrayList<>();
private List<MovieCelebrityResponse> directors = new ArrayList<>();
// Constructors, getters and setters
public void setCelebrityRoles(List<MovieCelebrityResponse> movieCelebrities) {
this.setCast(movieCelebrities.stream().filter(movieCelebrity -> movieCelebrity.getRole().equals(CelebrityRole.ACTOR)).collect(Collectors.toList()));
this.setDirectors(movieCelebrities.stream().filter(movieCelebrity -> movieCelebrity.getRole().equals(CelebrityRole.DIRECTOR)).collect(Collectors.toList()));
this.setWriters(movieCelebrities.stream().filter(movieCelebrity -> movieCelebrity.getRole().equals(CelebrityRole.WRITER)).collect(Collectors.toList()));
}
}
如您所见,我将movieCelebrities列表分为3个列表(广播,Directos和作家)
然后将电影映射到 MovieResponse ,我正在使用ModelMapper
类:
public class ModelMapper {
public static MovieResponse mapMovieToMovieResponse(Movie movie) {
// Create a new MovieResponse and Assign the Movie data to MovieResponse
MovieResponse movieResponse = new MovieResponse(movie.getId(), movie.getName(), movie.getReleaseDate(),
movie.getRuntime(),movie.getRating(), movie.getStoryline(), movie.getPoster(), movie.getRated());
// Get MovieCelebrities for current Movie
List<MovieCelebrityResponse> movieCelebrityResponses = movie.getMovieCelebrities().stream().map(movieCelebrity -> {
// Get Celebrity for current MovieCelebrities
CelebrityResponse celebrityResponse = new CelebrityResponse(movieCelebrity.getCelebrity().getId(),
movieCelebrity.getCelebrity().getName(), movieCelebrity.getCelebrity().getPicture(),
movieCelebrity.getCelebrity().getDateOfBirth(), movieCelebrity.getCelebrity().getBiography(), null);
return new MovieCelebrityResponse(movieCelebrity.getId(), movieCelebrity.getRole(),movieCelebrity.getCharacterName(), null, celebrityResponse);
}).collect(Collectors.toList());
// Assign movieCelebrityResponse to movieResponse
movieResponse.setCelebrityRoles(movieCelebrityResponses);
return movieResponse;
}
}
最后是我在控制器中调用的我的 MovieService 服务:
@Service
public class MovieServiceImpl implements MovieService {
private MovieRepository movieRepository;
@Autowired
public void setMovieRepository(MovieRepository movieRepository) {
this.movieRepository = movieRepository;
}
public PagedResponse<MovieResponse> getAllMovies(Pageable pageable) {
Page<Movie> movies = movieRepository.findAll(pageable);
if(movies.getNumberOfElements() == 0) {
return new PagedResponse<>(Collections.emptyList(), movies.getNumber(),
movies.getSize(), movies.getTotalElements(), movies.getTotalPages(), movies.isLast());
}
List<MovieResponse> movieResponses = movies.map(ModelMapper::mapMovieToMovieResponse).getContent();
return new PagedResponse<>(movieResponses, movies.getNumber(),
movies.getSize(), movies.getTotalElements(), movies.getTotalPages(), movies.isLast());
}
}
所以这里的问题是:对于每个模型都可以使用,我有一个用于json序列化的有效负载类吗?还是有更好的方法。
伙计们id我的代码有任何问题,请随时发表评论。
答案 0 :(得分:1)
我们应该使每一层彼此分开。与您的情况一样,您已经定义了实体和响应类。这是分离事物的正确方法,我们永远不要在响应中发送实体。即使是要求的东西,我们也应该上课。
如果我们发送实体而不是响应dto,会出现什么问题。
不可用于修改它们,因为我们已经向客户端公开了它
有时我们不想序列化某些字段并作为响应发送。
存在一些将请求转换为域,将实体转换为域等的开销。但是可以保持组织性更好。 ModelMapper是实现翻译目的的最佳选择。
尝试使用构造注入而不是setter来实现任务依赖。
答案 1 :(得分:1)
始终建议将DTO和实体分开。 即使的结构,实体也应与 DB / ORM 进行交互,并且 DTO 应与客户端层(用于请求和响应的层)进行交互实体和 DTO 相同。
此处的实体为Movie
,
DTO为MovieResponse
使用您现有的课程MovieResponse
进行请求和响应。
切勿使用Movie
类进行请求和响应。
并且类MovieServiceImpl
应该包含用于将实体转换为 DTO 的业务逻辑,或者您可以使用 Dozer API进行自动转换。
分离的原因:
答案 2 :(得分:1)
DTO是一种设计模式,它解决了从服务中获取最大有用数据的问题。
在像您这样的简单应用程序的情况下,DTO往往类似于Entity类。但是,对于某些复杂的应用程序,可以扩展DTO以合并来自各种实体的数据,从而避免对服务器的多次请求,从而节省了宝贵的资源和请求响应时间。
我建议不要在像这样的简单情况下重复代码,并使用模型类来响应API。将单独的响应类用作DTO不会解决任何目的,只会使代码维护变得困难。
答案 3 :(得分:1)
一方面,您应该将它们分开,因为有时在模型中使用的某些JPA注释不能与json处理器注释配合使用。是的,您应该将事情分开。
如果您以后决定更改数据层怎么办?您是否需要重写所有客户端?
另一方面,存在映射问题。为此,您可以使用性能损失较小的库。
答案 4 :(得分:1)
不久以前我就遇到了这个难题,这是我的思考过程。我在这里https://stackoverflow.com/questions/44572188/microservices-restful-api-dtos-or-not
编写的代码越少,产生的错误越少。
可维护性-更少的样板代码。
从长远来看,DTO将为您提供更大的灵活性
域对象因注释而肿。
mybatis.configuration.map-underscore-to-camel-case: true
,spring.jackson.property-naming-strategy: SNAKE_CASE
短篇小说,至少就我而言,缺点并没有超过优点,因此以新的POJO作为DTO来重复自己毫无意义。更少的代码,更少的错误机会。因此,继续公开Domain对象,没有单独的“ view”对象。
免责声明:这可能适用于您的用例,也可能不适用。这个观察是我的用例(基本上是具有15个端点的CRUD api)
答案 5 :(得分:0)
尽管大多数人回答了使用DTO对象的利弊,但我想给我2美分。在我的情况下,DTO是必需的,因为并非从用户捕获数据库中所有持久化的字段。有一些字段是根据(其他字段的)用户输入来计算的,并且不会向用户公开。而且,它还可以减小有效负载的大小,从而在这种情况下可以提高性能。
答案 6 :(得分:0)
我主张将“Payload”或“Data”对象与“Model”或“Display”对象分开。几乎总是。这只会让事情更容易管理。
这是一个例子: 假设您需要访问一个 API,该 API 可为您提供有关待售猫的数据。然后将数据解析为猫模型对象并填充猫的列表,然后将其显示给用户。很酷。
但是现在您想要集成另一个 API 并从 2 个数据库中拉猫。但是你遇到了一个问题。一个 API 返回 furColor
表示颜色,而新 API 返回 catColor
表示颜色。
如果您使用相同的对象也显示信息,您有一些选择:
furColor
和 catColor
都添加到模型对象中,使它们都是可选的,并执行某种计算属性来检查设置了哪个并使用那个来显示颜色
但是,如果您创建一个捕获响应的数据对象,然后创建一个仅包含填充列表所需信息的显示对象,这将变得非常简单:
这也将使在本地存储数据更加干净。