{"id":373379,"date":"2024-05-21T05:34:49","date_gmt":"2024-05-21T05:34:49","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=373379"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=373379","title":{"rendered":"<span>\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b \u043d\u0430 \u0434\u0436\u0435\u043d\u0435\u0440\u0438\u043a\u0430\u0445: \u043f\u0438\u0448\u0435\u043c \u043a\u043e\u0434\u0430 \u0432 3 \u0440\u0430\u0437\u0430 \u043c\u0435\u043d\u044c\u0448\u0435<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u043d\u0430\u0448\u0438\u0445 Java \u043a\u0443\u0440\u0441\u043e\u0432 &#171;\u0418\u0437 Middle \u0432 Senior&#187; (\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0435 \u043f\u043e\u0441\u0442\u044b\u00a0<a href=\"https:\/\/habr.com\/ru\/articles\/777824\/\" rel=\"noopener noreferrer nofollow\">\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u044f Java Spring Boot \u043d\u0430 Kotlin<\/a>\u00a0\u0438\u00a0<a href=\"https:\/\/habr.com\/ru\/articles\/765332\/\" rel=\"noopener noreferrer nofollow\">\u00ab\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438 \u0432 Java\u00bb<\/a>) \u043d\u0435\u0434\u0430\u0432\u043d\u043e \u0432\u044b\u0448\u0435\u043b \u043d\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 <strong>Startup<\/strong>: Spring Boot \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u0445\u043e\u0441\u0442\u0438\u043d\u0433\u043e\u043c \u0438 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043e\u0439 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u044d\u0432\u043e\u043b\u044e\u0446\u0438\u0438 \u043d\u0430\u0448\u0435\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u043e\u043d\u043b\u0430\u0439\u043d-\u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f \u0441 2016\u0433. <br \/>\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u043a\u0443\u0440\u0441\u0430 \u0435\u0441\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u0432, \u0441\u043e\u043a\u0440\u0430\u0449\u0430\u044e\u0449\u0438\u0445 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u043e\u0434\u0430\/\u0443\u0441\u0438\u043b\u0438\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432. \u041e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445: \u0441\u043a\u0432\u043e\u0437\u043d\u0430\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043e\u0442 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0434\u043e \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u0432, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0430\u044f \u0441\u043e\u043a\u0440\u0430\u0449\u0430\u0442\u044c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u043e\u0434\u0430 ~3\u0445. \u041a\u043e\u0434 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u043d\u0430 Java, \u043d\u043e \u043e\u0431\u0449\u0438\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0432 \u043b\u044e\u0431\u043e\u043c \u044f\u0437\u044b\u043a\u0435 \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439. \u041a\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e &#8212; \u0434\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c.<\/p>\n<h3>\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438<\/h3>\n<p>\u0412\u0441\u0435, \u043a\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441\u043e\u00a0<a href=\"https:\/\/habr.com\/ru\/articles\/232381#datajpa\" rel=\"noopener noreferrer nofollow\">Spring Data<\/a>\u00a0\u0437\u043d\u0430\u0435\u0442, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u043b\u043e\u0441\u044c \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0437\u0430 \u0441\u0447\u0435\u0442 \u0433\u043e\u0442\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0411\u0414. \u041c\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c\u00a0<a href=\"https:\/\/stackoverflow.com\/questions\/42781264\/548473\" rel=\"noopener noreferrer nofollow\">\u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u043d\u0430\u0441\u043b\u0435\u0434\u043d\u0438\u043a\u0438 \u044d\u0442\u0438\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432<\/a>, \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u044f \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f JPA:<\/p>\n<pre><code class=\"java\">@NoRepositoryBean public interface BaseRepository&lt;T> extends JpaRepository&lt;T, Integer> {      @Transactional     @Modifying     @Query(\"DELETE FROM #{#entityName} e WHERE e.id=:id\")     int delete(int id);      @SuppressWarnings(\"all\") \/\/ transaction invoked     default void deleteExisted(int id) {         if (delete(id) == 0) {             throw new NotFoundException(\"Entity with id=\" + id + \" not found\");         }     }      default T getExisted(int id) {         return findById(id).orElseThrow(() -> new NotFoundException(\"Entity with id=\" + id + \" not found\"));     } }<\/code><\/pre>\n<h3>\u041c\u0430\u043f\u043f\u0435\u0440\u044b<\/h3>\n<p>\u041e\u0431\u044b\u0447\u043d\u043e \u0432 \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043c\u043d\u043e\u0433\u043e \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0439 <code>Entity &lt;-> Transfer Object (TO)<\/code>. \u0414\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u044d\u0442\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u0435\u0441\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a-\u043c\u0430\u043f\u043f\u0435\u0440\u043e\u0432. \u041c\u043d\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0430\u0432\u0442\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043a\u043e\u0434\u0430\u00a0<a href=\"https:\/\/mapstruct.org\/\" rel=\"noopener noreferrer nofollow\">MapStruct<\/a>. \u041a\u0440\u043e\u043c\u0435 \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u0430, MapStruct \u0442\u0430\u043a\u0436\u0435 \u0443\u043c\u0435\u0435\u0442 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043a\u0438 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u043b\u044f \u043a\u043b\u0430\u0441\u0441\u043e\u0432. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043c\u0430\u043f\u043f\u0435\u0440\u043e\u0432:<\/p>\n<pre><code class=\"java\">public interface BaseMapper&lt;E, T> {      E toEntity(T to);      List&lt;E> toEntityList(Collection&lt;T> tos);      E updateFromTo(T to, @MappingTarget E entity);      T toTo(E entity);      List&lt;T> toToList(Collection&lt;E> entities); }<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u0449\u0443\u044e\u00a0<a href=\"https:\/\/mapstruct.org\/documentation\/stable\/reference\/html\/#configuration-options\" rel=\"noopener noreferrer nofollow\">\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043c\u0430\u043f\u043f\u0435\u0440\u043e\u0432<\/a>. \u0417\u0434\u0435\u0441\u044c \u043c\u0430\u043f\u043f\u0435\u0440\u044b \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u043a\u0430\u043a \u0431\u0438\u043d\u044b Spring \u0438 \u043d\u0430 \u043d\u0435\u0437\u0430\u043c\u0430\u043f\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044f \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u044f \u043d\u0435 \u0432\u044b\u0434\u0430\u044e\u0442\u0441\u044f:  <\/p>\n<pre><code class=\"java\">@MapperConfig(         componentModel = MappingConstants.ComponentModel.SPRING,         unmappedTargetPolicy = ReportingPolicy.IGNORE ) public interface MapStructConfig { }<\/code><\/pre>\n<p>\u041f\u043e\u043b\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u0430\u043f\u043f\u044f\u0442\u0441\u044f 1:1 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435 \u043d\u0430\u0434\u043e, \u0434\u043b\u044f \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u0435\u0441\u0442\u044c\u00a0<a href=\"https:\/\/mapstruct.org\/documentation\/stable\/reference\/html\/#defining-mapper\" rel=\"noopener noreferrer nofollow\">\u0440\u0430\u0437\u043d\u044b\u0435 \u043e\u043f\u0446\u0438\u0438<\/a>.\u00a0\u041f\u0440\u0438\u043c\u0435\u0440 \u043c\u0430\u043f\u043f\u0435\u0440\u0430\u00a0<code>User<\/code>\u00a0&lt;->\u00a0<code>UserTo<\/code>:  <\/p>\n<pre><code class=\"java\">@Mapper(config = MapStructConfig.class) public interface UserMapper extends BaseMapper&lt;User, UserTo> {      @Mapping(target = \"email\", expression = \"java(to.getEmail().toLowerCase())\")     @Override     User toEntity(UserTo to);      @Mapping(target = \"id\", ignore = true)     @Mapping(target = \"email\", expression = \"java(to.getEmail().toLowerCase())\")     @Override     User updateFromTo(UserTo to, @MappingTarget User entity); }<\/code><\/pre>\n<p>\u041c\u0430\u043f\u043f\u0435\u0440\u044b \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043d\u0430 \u0444\u0430\u0437\u0435\u00a0<em>compile<\/em>, \u043f\u0440\u0438 \u0441\u0431\u043e\u0440\u043a\u0435 maven \u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435\u00a0<code>\\target\\generated-sources<\/code>\u00a0\u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043a\u043e\u0434 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438. \u0415\u0441\u043b\u0438 \u043c\u0430\u043f\u043f\u0438\u043d\u0433 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c 1:1 \u0431\u0435\u0437 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u0434\u0441\u0442\u0440\u043e\u0435\u043a, \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u043c\u0435\u0442\u043e\u0434\u044b\u00a0<code>BaseMapper<\/code>\u00a0\u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f.<\/p>\n<h3>\u041e\u0431\u0449\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u044b \u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b \u0434\u0430\u043d\u043d\u044b\u0445<\/h3>\n<p>\u0421\u0434\u0435\u043b\u0430\u0435\u043c \u043e\u0431\u0449\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u044b \u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b \u0434\u043b\u044f \u0434\u0430\u043d\u043d\u044b\u0445, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u0435.\u00a0<code>equals\/hashCode<\/code>\u00a0\u0434\u043b\u044f \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435\u00a0<a href=\"https:\/\/jpa-buddy.com\/blog\/hopefully-the-final-article-about-equals-and-hashcode-for-jpa-entities-with-db-generated-ids\/\" rel=\"noopener noreferrer nofollow\">\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0439 \u043e\u0442 jpa buddy<\/a><\/p>\n<pre><code class=\"java\">public interface HasId {     Integer getId();      void setId(Integer id);      @JsonIgnore     default boolean isNew() {         return getId() == null;     }      \/\/ doesn't work for hibernate lazy proxy     default int id() {         Assert.notNull(getId(), \"Entity must has id\");         return getId();     } }  @NoArgsConstructor @AllArgsConstructor(access = AccessLevel.PROTECTED) @Data public abstract class BaseTo implements HasId {     @Schema(accessMode = Schema.AccessMode.READ_ONLY) \/\/ https:\/\/stackoverflow.com\/a\/28025008\/548473     protected Integer id;      @Override     public String toString() {         return getClass().getSimpleName() + \":\" + id;     } }  @MappedSuperclass @Access(AccessType.FIELD) @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PROTECTED) public abstract class BaseEntity implements HasId {      @Id     @GeneratedValue(strategy = GenerationType.IDENTITY)     @Schema(accessMode = Schema.AccessMode.READ_ONLY) \/\/ https:\/\/stackoverflow.com\/a\/28025008\/548473     protected Integer id;      \/\/  https:\/\/jpa-buddy.com\/blog\/hopefully-the-final-article-about-equals-and-hashcode-for-jpa-entities-with-db-generated-ids\/     @Override     public boolean equals(Object o) {         if (this == o) return true;         if (o == null || getEffectiveClass(this) != getEffectiveClass(o)) return false;         return getId() != null &amp;&amp; getId().equals(((BaseEntity) o).getId());     }      @Override     public final int hashCode() {         return getEffectiveClass(this).hashCode();     }      @Override     public String toString() {         return getClass().getSimpleName() + \":\" + id;     } }<\/code><\/pre>\n<h3>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0443\u0442\u0438\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438<\/h3>\n<pre><code class=\"java\">@UtilityClass public class Util {     public static Class getEffectiveClass(Object o) {         return o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();     } }  @UtilityClass public class ValidationUtil {      public static void checkNew(HasId bean) {         if (!bean.isNew()) {             throw new IllegalRequestDataException(bean.getClass().getSimpleName() + \" must be new (id=null)\");         }     }      \/\/  Conservative when you reply, but accept liberally (http:\/\/stackoverflow.com\/a\/32728226\/548473)     public static void assureIdConsistent(HasId bean, int id) {         if (bean.isNew()) {             bean.setId(id);         } else if (bean.id() != id) {             throw new IllegalRequestDataException(bean.getClass().getSimpleName() + \" must has id=\" + id);         }     } }<\/code><\/pre>\n<h3>\u0421\u0435\u0440\u0432\u0438\u0441\u044b<\/h3>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u0432\u044f\u0437\u0430\u0442\u044c \u0432\u043c\u0435\u0441\u0442\u0435 \u043c\u0430\u043f\u043f\u0435\u0440\u044b \u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438, \u043f\u043e\u043b\u0443\u0447\u0438\u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0441 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0447\u0430\u0441\u0442\u044b\u043c\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u0432. \u0418\u043d\u043e\u0433\u0434\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0438\u043b\u0438 \u043f\u0440\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0438\u0437 <code>Entity<\/code> \u0442\u0440\u0435\u0431\u0443\u044e\u0442\u0441\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f <code>BaseService.prepareForSave<\/code>\u00a0 \u0438 <code>BaseService.prepareForUpdate<\/code>\u00a0(\u043f\u0440\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0438\u0437 <code>TO<\/code> \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0435\u043b\u0430\u044e\u0442\u0441\u044f \u0432 \u043c\u0430\u043f\u043f\u0435\u0440\u0435). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043c\u0430\u043f\u043f\u0435\u0440\u0430 <code>&lt;M><\/code> \u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f <code>&lt;R><\/code> \u0434\u0430\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0431\u0440\u0430\u0442\u044c \u0438\u0445 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043a\u0430\u0441\u0442\u0438\u043d\u0433\u0430:<\/p>\n<pre><code class=\"java\">public class BaseService&lt;E extends HasId, T extends BaseTo, R extends BaseRepository&lt;E>, M extends BaseMapper&lt;E, T>> {     protected final Logger log = LoggerFactory.getLogger(getClass());      public BaseService(R repository, M mapper) {         this(repository, mapper, null, null);     }      public BaseService(R repository, M mapper,                        Function&lt;E, E> prepareForSave, BiFunction&lt;E, E, E> prepareForUpdate) {         this.repository = repository;         this.mapper = mapper;         this.prepareForSave = prepareForSave;         this.prepareForUpdate = prepareForUpdate;     }      @Getter     protected final R repository;     @Getter     protected final M mapper;     private final Function&lt;E, E> prepareForSave;     private final BiFunction&lt;E, E, E> prepareForUpdate;      public T getTo(int id) {         log.info(\"getTo by id={}\", id);         return toTo(repository.getExisted(id));     }      public E get(int id) {         log.info(\"get by id={}\", id);         return repository.getExisted(id);     }      public List&lt;E> getAll() {         return getAll(Sort.unsorted());     }      public List&lt;E> getAll(Sort sort) {         log.info(\"getAll\");         return repository.findAll(sort);     }      public List&lt;T> getAllTos() {         return getAllTos(Sort.unsorted());     }      public List&lt;T> getAllTos(Sort sort) {         log.info(\"getAllTos\");         return toToList(repository.findAll(sort));     }      public E createFromTo(T to) {         log.info(\"createFromTo {}\", to);         ValidationUtil.checkNew(to);         E entity = toEntity(to);         if (prepareForSave != null) entity = prepareForSave.apply(entity);         return repository.save(entity);     }      public E create(E entity) {         log.info(\"create {}\", entity);         ValidationUtil.checkNew(entity);         if (prepareForSave != null) entity = prepareForSave.apply(entity);         return repository.save(entity);     }      public void delete(int id) {         log.info(\"delete by id={}\", id);         repository.deleteExisted(id);     }      @Transactional     public E update(E entity, int id) {         log.info(\"update {} with id={}\", entity, id);         ValidationUtil.assureIdConsistent(entity, id);         if (prepareForUpdate != null) {             E dbEntity = repository.getExisted(entity.id());             entity = prepareForUpdate.apply(entity, dbEntity);         }         return repository.save(entity);     }      @Transactional     public E updateFromTo(T to, int id) {         log.info(\"updateFromTo {} with id={}\", to, id);         ValidationUtil.assureIdConsistent(to, id);         E dbEntity = repository.getExisted(to.id());         return repository.save(updateFromTo(to, dbEntity));     }      \/\/ delegate to mapper     public E toEntity(T to) {         return mapper.toEntity(to);     }      public List&lt;E> toEntityList(Collection&lt;T> tos) {         return mapper.toEntityList(tos);     }      public E updateFromTo(T to, E entity) {         return mapper.updateFromTo(to, entity);     }      public T toTo(E entity) {         return mapper.toTo(entity);     }      public List&lt;T> toToList(List&lt;E> entities) {         return mapper.toToList(entities);     } }<\/code><\/pre>\n<h3>\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b<\/h3>\n<p>\u041e\u0431\u0449\u0438\u0439 \u043a\u043e\u0434 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0442\u0432\u0435\u0442\u043e\u0432 POST \u0432\u044b\u043d\u0435\u0441\u0435\u043c \u0432\u00a0<code>WebUtil<\/code>:<\/p>\n<pre><code class=\"java\">@UtilityClass public class WebUtil {     \/\/ create ResponseEntity     public static &lt;T extends HasId> ResponseEntity&lt;T> createdResponse(String url, T created) {         return createdResponse(url + \"\/{id}\", created, created.getId());     }      public static &lt;T extends HasId> ResponseEntity&lt;T> createdResponse(String url, T created, Object... params) {         URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath()                 .path(url).buildAndExpand(params).toUri();         return ResponseEntity.created(uriOfNewResource).body(created);     } }<\/code><\/pre>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0434\u0430 \u043d\u0430\u043c \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e REST \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430:<\/p>\n<pre><code class=\"java\">@RestController @RequestMapping(value = AdminUserController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE) @Slf4j public class AdminUserController {     @Autowired     protected UserService service;      static final String REST_URL = SecurityConfig.API_PATH + \"\/admin\/users\";      @GetMapping(\"\/{id}\")     public User get(@PathVariable int id) {         return service.get(id);     }      @DeleteMapping(\"\/{id}\")     @ResponseStatus(HttpStatus.NO_CONTENT)     public void delete(@PathVariable int id) {         service.delete(id);     }      @GetMapping     public List&lt;User> getAll() {         log.info(\"getAll\");         return service.getAll(Sort.by(Sort.Direction.ASC, \"email\"));     }      @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)     public ResponseEntity&lt;User> createWithLocation(@Valid @RequestBody User user) {         User created = service.create(user);         return createdResponse(REST_URL, created);     }      @PutMapping(value = \"\/{id}\", consumes = MediaType.APPLICATION_JSON_VALUE)     @ResponseStatus(HttpStatus.NO_CONTENT)     public void update(@Valid @RequestBody User user, @PathVariable int id) {         service.update(user, id);     }      @GetMapping(\"\/by-email\")     public User getByEmail(@RequestParam String email) {         log.info(\"getByEmail {}\", email);         return service.getRepository().getExistedByEmail(email);     } }<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0435\u043a\u0442 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u043e\u0432 \u043c\u043d\u043e\u0433\u043e, \u044d\u043d\u043e\u043d\u043e\u043c\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u0430\u044f. \u041c\u0435\u043d\u044c\u0448\u0435 \u043a\u043e\u0434\u0430, \u043c\u0435\u043d\u044c\u0448\u0435 \u043e\u0448\u0438\u0431\u043e\u043a, \u043f\u0440\u043e\u0449\u0435 \u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u0435\u0435 \u043a\u043e\u0434.<\/p>\n<p>\u041f\u0440\u0438\u044f\u0442\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043f\u0440\u0438\u0433\u043b\u0430\u0448\u0430\u0435\u043c \u043d\u0430 \u043d\u0430\u0448\u0438 \u043a\u0443\u0440\u0441\u044b!  <\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/807047\/\"> https:\/\/habr.com\/ru\/articles\/807047\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u043d\u0430\u0448\u0438\u0445 Java \u043a\u0443\u0440\u0441\u043e\u0432 &#171;\u0418\u0437 Middle \u0432 Senior&#187; (\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0435 \u043f\u043e\u0441\u0442\u044b\u00a0<a href=\"https:\/\/habr.com\/ru\/articles\/777824\/\" rel=\"noopener noreferrer nofollow\">\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u044f Java Spring Boot \u043d\u0430 Kotlin<\/a>\u00a0\u0438\u00a0<a href=\"https:\/\/habr.com\/ru\/articles\/765332\/\" rel=\"noopener noreferrer nofollow\">\u00ab\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438 \u0432 Java\u00bb<\/a>) \u043d\u0435\u0434\u0430\u0432\u043d\u043e \u0432\u044b\u0448\u0435\u043b \u043d\u043e\u0432\u044b\u0439 \u043a\u0443\u0440\u0441 <strong>Startup<\/strong>: Spring Boot \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u0445\u043e\u0441\u0442\u0438\u043d\u0433\u043e\u043c \u0438 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043e\u0439 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u044d\u0432\u043e\u043b\u044e\u0446\u0438\u0438 \u043d\u0430\u0448\u0435\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u043e\u043d\u043b\u0430\u0439\u043d-\u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f \u0441 2016\u0433. <br \/>\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u043a\u0443\u0440\u0441\u0430 \u0435\u0441\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u0432, \u0441\u043e\u043a\u0440\u0430\u0449\u0430\u044e\u0449\u0438\u0445 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u043e\u0434\u0430\/\u0443\u0441\u0438\u043b\u0438\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432. \u041e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445: \u0441\u043a\u0432\u043e\u0437\u043d\u0430\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043e\u0442 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0434\u043e \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u0432, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0430\u044f \u0441\u043e\u043a\u0440\u0430\u0449\u0430\u0442\u044c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u043e\u0434\u0430 ~3\u0445. \u041a\u043e\u0434 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u043d\u0430 Java, \u043d\u043e \u043e\u0431\u0449\u0438\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0432 \u043b\u044e\u0431\u043e\u043c \u044f\u0437\u044b\u043a\u0435 \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439. \u041a\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e &#8212; \u0434\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c.<\/p>\n<h3>\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438<\/h3>\n<p>\u0412\u0441\u0435, \u043a\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441\u043e\u00a0<a href=\"https:\/\/habr.com\/ru\/articles\/232381#datajpa\" rel=\"noopener noreferrer nofollow\">Spring Data<\/a>\u00a0\u0437\u043d\u0430\u0435\u0442, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u043b\u043e\u0441\u044c \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0437\u0430 \u0441\u0447\u0435\u0442 \u0433\u043e\u0442\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0411\u0414. \u041c\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c\u00a0<a href=\"https:\/\/stackoverflow.com\/questions\/42781264\/548473\" rel=\"noopener noreferrer nofollow\">\u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u043d\u0430\u0441\u043b\u0435\u0434\u043d\u0438\u043a\u0438 \u044d\u0442\u0438\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432<\/a>, \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u044f \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f JPA:<\/p>\n<pre><code class=\"java\">@NoRepositoryBean public interface BaseRepository&lt;T> extends JpaRepository&lt;T, Integer> {      @Transactional     @Modifying     @Query(\"DELETE FROM #{#entityName} e WHERE e.id=:id\")     int delete(int id);      @SuppressWarnings(\"all\") \/\/ transaction invoked     default void deleteExisted(int id) {         if (delete(id) == 0) {             throw new NotFoundException(\"Entity with id=\" + id + \" not found\");         }     }      default T getExisted(int id) {         return findById(id).orElseThrow(() -> new NotFoundException(\"Entity with id=\" + id + \" not found\"));     } }<\/code><\/pre>\n<h3>\u041c\u0430\u043f\u043f\u0435\u0440\u044b<\/h3>\n<p>\u041e\u0431\u044b\u0447\u043d\u043e \u0432 \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043c\u043d\u043e\u0433\u043e \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0439 <code>Entity &lt;-> Transfer Object (TO)<\/code>. \u0414\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u044d\u0442\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u0435\u0441\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a-\u043c\u0430\u043f\u043f\u0435\u0440\u043e\u0432. \u041c\u043d\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0430\u0432\u0442\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043a\u043e\u0434\u0430\u00a0<a href=\"https:\/\/mapstruct.org\/\" rel=\"noopener noreferrer nofollow\">MapStruct<\/a>. \u041a\u0440\u043e\u043c\u0435 \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u0430, MapStruct \u0442\u0430\u043a\u0436\u0435 \u0443\u043c\u0435\u0435\u0442 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043a\u0438 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u043b\u044f \u043a\u043b\u0430\u0441\u0441\u043e\u0432. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043c\u0430\u043f\u043f\u0435\u0440\u043e\u0432:<\/p>\n<pre><code class=\"java\">public interface BaseMapper&lt;E, T> {      E toEntity(T to);      List&lt;E> toEntityList(Collection&lt;T> tos);      E updateFromTo(T to, @MappingTarget E entity);      T toTo(E entity);      List&lt;T> toToList(Collection&lt;E> entities); }<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u0449\u0443\u044e\u00a0<a href=\"https:\/\/mapstruct.org\/documentation\/stable\/reference\/html\/#configuration-options\" rel=\"noopener noreferrer nofollow\">\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043c\u0430\u043f\u043f\u0435\u0440\u043e\u0432<\/a>. \u0417\u0434\u0435\u0441\u044c \u043c\u0430\u043f\u043f\u0435\u0440\u044b \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u043a\u0430\u043a \u0431\u0438\u043d\u044b Spring \u0438 \u043d\u0430 \u043d\u0435\u0437\u0430\u043c\u0430\u043f\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044f \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u044f \u043d\u0435 \u0432\u044b\u0434\u0430\u044e\u0442\u0441\u044f:  <\/p>\n<pre><code class=\"java\">@MapperConfig(         componentModel = MappingConstants.ComponentModel.SPRING,         unmappedTargetPolicy = ReportingPolicy.IGNORE ) public interface MapStructConfig { }<\/code><\/pre>\n<p>\u041f\u043e\u043b\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u0430\u043f\u043f\u044f\u0442\u0441\u044f 1:1 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435 \u043d\u0430\u0434\u043e, \u0434\u043b\u044f \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u0435\u0441\u0442\u044c\u00a0<a href=\"https:\/\/mapstruct.org\/documentation\/stable\/reference\/html\/#defining-mapper\" rel=\"noopener noreferrer nofollow\">\u0440\u0430\u0437\u043d\u044b\u0435 \u043e\u043f\u0446\u0438\u0438<\/a>.\u00a0\u041f\u0440\u0438\u043c\u0435\u0440 \u043c\u0430\u043f\u043f\u0435\u0440\u0430\u00a0<code>User<\/code>\u00a0&lt;->\u00a0<code>UserTo<\/code>:  <\/p>\n<pre><code class=\"java\">@Mapper(config = MapStructConfig.class) public interface UserMapper extends BaseMapper&lt;User, UserTo> {      @Mapping(target = \"email\", expression = \"java(to.getEmail().toLowerCase())\")     @Override     User toEntity(UserTo to);      @Mapping(target = \"id\", ignore = true)     @Mapping(target = \"email\", expression = \"java(to.getEmail().toLowerCase())\")     @Override     User updateFromTo(UserTo to, @MappingTarget User entity); }<\/code><\/pre>\n<p>\u041c\u0430\u043f\u043f\u0435\u0440\u044b \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043d\u0430 \u0444\u0430\u0437\u0435\u00a0<em>compile<\/em>, \u043f\u0440\u0438 \u0441\u0431\u043e\u0440\u043a\u0435 maven \u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435\u00a0<code>\\target\\generated-sources<\/code>\u00a0\u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043a\u043e\u0434 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438. \u0415\u0441\u043b\u0438 \u043c\u0430\u043f\u043f\u0438\u043d\u0433 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c 1:1 \u0431\u0435\u0437 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u0434\u0441\u0442\u0440\u043e\u0435\u043a, \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u043c\u0435\u0442\u043e\u0434\u044b\u00a0<code>BaseMapper<\/code>\u00a0\u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f.<\/p>\n<h3>\u041e\u0431\u0449\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u044b \u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b \u0434\u0430\u043d\u043d\u044b\u0445<\/h3>\n<p>\u0421\u0434\u0435\u043b\u0430\u0435\u043c \u043e\u0431\u0449\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u044b \u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b \u0434\u043b\u044f \u0434\u0430\u043d\u043d\u044b\u0445, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u0435.\u00a0<code>equals\/hashCode<\/code>\u00a0\u0434\u043b\u044f \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435\u00a0<a href=\"https:\/\/jpa-buddy.com\/blog\/hopefully-the-final-article-about-equals-and-hashcode-for-jpa-entities-with-db-generated-ids\/\" rel=\"noopener noreferrer nofollow\">\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0439 \u043e\u0442 jpa buddy<\/a><\/p>\n<pre><code class=\"java\">public interface HasId {     Integer getId();      void setId(Integer id);      @JsonIgnore     default boolean isNew() {         return getId() == null;     }      \/\/ doesn't work for hibernate lazy proxy     default int id() {         Assert.notNull(getId(), \"Entity must has id\");         return getId();     } }  @NoArgsConstructor @AllArgsConstructor(access = AccessLevel.PROTECTED) @Data public abstract class BaseTo implements HasId {     @Schema(accessMode = Schema.AccessMode.READ_ONLY) \/\/ https:\/\/stackoverflow.com\/a\/28025008\/548473     protected Integer id;      @Override     public String toString() {         return getClass().getSimpleName() + \":\" + id;     } }  @MappedSuperclass @Access(AccessType.FIELD) @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PROTECTED) public abstract class BaseEntity implements HasId {      @Id     @GeneratedValue(strategy = GenerationType.IDENTITY)     @Schema(accessMode = Schema.AccessMode.READ_ONLY) \/\/ https:\/\/stackoverflow.com\/a\/28025008\/548473     protected Integer id;      \/\/  https:\/\/jpa-buddy.com\/blog\/hopefully-the-final-article-about-equals-and-hashcode-for-jpa-entities-with-db-generated-ids\/     @Override     public boolean equals(Object o) {         if (this == o) return true;         if (o == null || getEffectiveClass(this) != getEffectiveClass(o)) return false;         return getId() != null &amp;&amp; getId().equals(((BaseEntity) o).getId());     }      @Override     public final int hashCode() {         return getEffectiveClass(this).hashCode();     }      @Override     public String toString() {         return getClass().getSimpleName() + \":\" + id;     } }<\/code><\/pre>\n<h3>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0443\u0442\u0438\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438<\/h3>\n<pre><code class=\"java\">@UtilityClass public class Util {     public static Class getEffectiveClass(Object o) {         return o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();     } }  @UtilityClass public class ValidationUtil {      public static void checkNew(HasId bean) {         if (!bean.isNew()) {             throw new IllegalRequestDataException(bean.getClass().getSimpleName() + \" must be new (id=null)\");         }     }      \/\/  Conservative when you reply, but accept liberally (http:\/\/stackoverflow.com\/a\/32728226\/548473)     public static void assureIdConsistent(HasId bean, int id) {         if (bean.isNew()) {             bean.setId(id);         } else if (bean.id() != id) {             throw new IllegalRequestDataException(bean.getClass().getSimpleName() + \" must has id=\" + id);         }     } }<\/code><\/pre>\n<h3>\u0421\u0435\u0440\u0432\u0438\u0441\u044b<\/h3>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u0432\u044f\u0437\u0430\u0442\u044c \u0432\u043c\u0435\u0441\u0442\u0435 \u043c\u0430\u043f\u043f\u0435\u0440\u044b \u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438, \u043f\u043e\u043b\u0443\u0447\u0438\u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0441 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0447\u0430\u0441\u0442\u044b\u043c\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u0432. \u0418\u043d\u043e\u0433\u0434\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0438\u043b\u0438 \u043f\u0440\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0438\u0437 <code>Entity<\/code> \u0442\u0440\u0435\u0431\u0443\u044e\u0442\u0441\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f <code>BaseService.prepareForSave<\/code>\u00a0 \u0438 <code>BaseService.prepareForUpdate<\/code>\u00a0(\u043f\u0440\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0438\u0437 <code>TO<\/code> \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0435\u043b\u0430\u044e\u0442\u0441\u044f \u0432 \u043c\u0430\u043f\u043f\u0435\u0440\u0435). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043c\u0430\u043f\u043f\u0435\u0440\u0430 <code>&lt;M><\/code> \u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f <code>&lt;R><\/code> \u0434\u0430\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0431\u0440\u0430\u0442\u044c \u0438\u0445 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043a\u0430\u0441\u0442\u0438\u043d\u0433\u0430:<\/p>\n<pre><code class=\"java\">public class BaseService&lt;E extends HasId, T extends BaseTo, R extends BaseRepository&lt;E>, M extends BaseMapper&lt;E, T>> {     protected final Logger log = LoggerFactory.getLogger(getClass());      public BaseService(R repository, M mapper) {         this(repository, mapper, null, null);     }      public BaseService(R repository, M mapper,                        Function&lt;E, E> prepareForSave, BiFunction&lt;E, E, E> prepareForUpdate) {         this.repository = repository;         this.mapper = mapper;         this.prepareForSave = prepareForSave;         this.prepareForUpdate = prepareForUpdate;     }      @Getter     protected final R repository;     @Getter     protected final M mapper;     private final Function&lt;E, E> prepareForSave;     private final BiFunction&lt;E, E, E> prepareForUpdate;      public T getTo(int id) {         log.info(\"getTo by id={}\", id);         return toTo(repository.getExisted(id));     }      public E get(int id) {         log.info(\"get by id={}\", id);         return repository.getExisted(id);     }      public List&lt;E> getAll() {         return getAll(Sort.unsorted());     }      public List&lt;E> getAll(Sort sort) {         log.info(\"getAll\");         return repository.findAll(sort);     }      public List&lt;T> getAllTos() {         return getAllTos(Sort.unsorted());     }      public List&lt;T> getAllTos(Sort sort) {         log.info(\"getAllTos\");         return toToList(repository.findAll(sort));     }      public E createFromTo(T to) {         log.info(\"createFromTo {}\", to);         ValidationUtil.checkNew(to);         E entity = toEntity(to);         if (prepareForSave != null) entity = prepareForSave.apply(entity);         return repository.save(entity);     }      public E create(E entity) {         log.info(\"create {}\", entity);         ValidationUtil.checkNew(entity);         if (prepareForSave != null) entity = prepareForSave.apply(entity);         return repository.save(entity);     }      public void delete(int id) {         log.info(\"delete by id={}\", id);         repository.deleteExisted(id);     }      @Transactional     public E update(E entity, int id) {         log.info(\"update {} with id={}\", entity, id);         ValidationUtil.assureIdConsistent(entity, id);         if (prepareForUpdate != null) {             E dbEntity = repository.getExisted(entity.id());             entity = prepareForUpdate.apply(entity, dbEntity);         }         return repository.save(entity);     }      @Transactional     public E updateFromTo(T to, int id) {         log.info(\"updateFromTo {} with id={}\", to, id);         ValidationUtil.assureIdConsistent(to, id);         E dbEntity = repository.getExisted(to.id());         return repository.save(updateFromTo(to, dbEntity));     }      \/\/ delegate to mapper     public E toEntity(T to) {         return mapper.toEntity(to);     }      public List&lt;E> toEntityList(Collection&lt;T> tos) {         return mapper.toEntityList(tos);     }      public E updateFromTo(T to, E entity) {         return mapper.updateFromTo(to, entity);     }      public T toTo(E entity) {         return mapper.toTo(entity);     }      public List&lt;T> toToList(List&lt;E> entities) {         return mapper.toToList(entities);     } }<\/code><\/pre>\n<h3>\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b<\/h3>\n<p>\u041e\u0431\u0449\u0438\u0439 \u043a\u043e\u0434 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0442\u0432\u0435\u0442\u043e\u0432 POST \u0432\u044b\u043d\u0435\u0441\u0435\u043c \u0432\u00a0<code>WebUtil<\/code>:<\/p>\n<pre><code class=\"java\">@UtilityClass public class WebUtil {     \/\/ create ResponseEntity     public static &lt;T extends HasId> ResponseEntity&lt;T> createdResponse(String url, T created) {         return createdResponse(url + \"\/{id}\", created, created.getId());     }      public static &lt;T extends HasId> ResponseEntity&lt;T><\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-373379","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/373379","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=373379"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/373379\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=373379"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=373379"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=373379"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}