Выберите объект с указанным естественным идентификатором, а не пытайтесь вставить (JPA и SpringBoot) - PullRequest
1 голос
/ 01 марта 2020

Так что в принципе у меня есть следующая потребность. Человек собирается отправить POST сущность, называемую «Отчет о расходах», через контроллер остатка.

У этой сущности есть поле Страна, которое фактически является ассоциацией с другой сущностью.

@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "EXPENSE_REPORTS")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ExpenseReport extends BaseEntity {

    @Column(name = "TRIP_DESCRIPTION", nullable = false)
    private String tripDescription;

    @Column(name = "JUSTIFICATION")
    private String justification;

    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "COUNTRY_ID")
    private Country country;

    @Column(name = "TRIP_START_DATE")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate tripStartDate;

    @Column(name = "TRIP_END_DATE")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate tripEndDate;

    @Column(name = "TOTAL_AMOUNT")
    private BigDecimal totalAmount;

    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name="USER_ID")
    private User user;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name="EXPENSE_ITEM_ID")
    private Set<ExpenseItem> expenses;

}
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "COUNTRIES")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Country extends BaseEntity {

    @Column(name = "NAME")
    @NaturalId
    private String name;
}

Я хотел проверить, возможно ли, что вызывающий абонент просто отправляет естественный идентификатор страны (в данном случае, имя) и JPA, вместо того, чтобы пытаться вставить эту страну, он находит страну с тем же именем и ассоциирует его.

Таким образом, вместо отправки этого сообщения:

{
    "tripDescription": "Some trip description...",
    "justification": "Some justification...",
    "country": {
      "id": 12345
    }
}

Он отправляет это:

{
    "tripDescription": "Some trip description...",
    "justification": "Some justification...",
    "country": {
      "name": "BR"
    }
}

Я могу представить, как это сделать, либо в хранилище реализации, но мне было интересно, есть ли что-нибудь автоматическое c, чтобы сделать это в JPA.

Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 02 марта 2020

Используйте ассоциацию, как показано ниже, вместо COUNTRIES.COUNTRY_ID используйте COUNTRIES.NAME

public class ExpenseReport extends BaseEntity {

    @Column(name = "TRIP_DESCRIPTION", nullable = false)
    private String tripDescription;

    @Column(name = "JUSTIFICATION")
    private String justification;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = “NAME”,referencedColumnName = "name" ) //Do not use COUNTRY_ID
    private Country country;
….
}
public class Country extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "NAME”,nullable = false, updatable = false, unique = true)
    @NaturalId(mutable = false)
    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (!(o instanceof Country)) {
            return false;
        }

        Product naturalIdCountry = (Country) o;
        return Objects.equals(getName(), naturalIdCountry.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getName());
    }
}
0 голосов
/ 02 марта 2020

Насколько я мог видеть, нет никакого автоматического c JPA способа сделать это. Я знаю, что это не очень хороший ответ, но я также не думаю, что, вероятно, будет способ сделать это автоматически в JPA (на данный момент), так как Hibernate очень сфокусирован на использовании первичных ключей и внешних ключей (т.е. суррогатные идентификаторы).

Как упоминает «curiousdev» в комментариях, используя код страны, например «BR» (который может быть non null unique, как первичный ключ), в качестве ключа (или joincolumn) хорошая альтернатива в этом случае. Существует целая дискуссия об этом здесь , если вам интересно.

Для моего собственного интереса я немного покопался в том, как может выглядеть реализация репозитория при использовании суррогатного идентификатора и естественного идентификатора. Сочетание кэша второго уровня и справочного поиска выглядит многообещающе. Вы можете избежать дополнительного запроса выбора в этом случае. Приведенный ниже код работает (с необходимыми зависимостями) и показывает то, что я нашел до сих пор.

Ссылка, о которой я говорю, находится в строке s.byNaturalId(Country.class).using("code", "NL").getReference();.

Кэш находится в настройках (hibernate.cache.use_second_level_cache и hibernate.cache.region.factory_class) и аннотациях @org.hibernate.annotations.Cache и @NaturalIdCache.

* / @ Slf4j publi c class NaturalIdRel {publi c stati c void main (String [] args) {try {new NaturalIdRel (). Test (); } catch (Exception e) {log.error ("Неудачные попытки.", e); }} void test () создает исключение {// https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#bootstrap Map settings = new HashMap <> (); // https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#configurations settings.put ("hibernate.dialect", H2Dialect.class.getName ()); settings.put ("hibernate.connection.url", "jdb c: h2: mem: test; database_to_upper = false; trace_level_system_out = 2"); settings.put ("hibernate.connection.username", "SA"); settings.put ("hibernate.connection.password", ""); settings.put ("hibernate.hbm2ddl.auto", "create"); settings.put ("hibernate.show_ sql", "true"); settings.put ("hibernate.cache.use_second_level_cache", "true"); settings.put ("hibernate.cache.region.factory_class", JCacheRegionFactory.class.getName ()); settings.put ("hibernate.cache.ehcache.missing_cache_strategy", "create"); settings.put ("hibernate.javax.cache.provider", EhcacheCachingProvider.class.getName ()); settings.put ("hibernate.javax.cache.missing_cache_strategy", "create"); //settings.put ("", ""); StandardServiceRegistry ssr = new StandardServiceRegistryBuilder () .applySettings (settings) .build (); Метаданные md = new MetadataSources (ssr) .addAnnotatedClass (ExpenseReport.class) .addAnnotatedClass (Country.class) .buildMetadata (); SessionFactory sf = md.getSessionFactoryBuilder () .build (); try {createCountry (sf); createExpense (SF); } finally {sf.close (); }} void createCountry (SessionFactory sf) {Страна c = новая Страна (); c .setCode ( "NL"); try (Session s = sf.openSession ()) {save (s, c); }} void createExpense (SessionFactory sf) {ExpenseReport er = new ExpenseReport (); er.setDescription ( "Расходы"); er.setReason ( "Fun"); // Просмотр (лог) вывода, не должно быть выбора для страны. try (Session s = sf.openSession ()) {// https://www.javacodegeeks.com/2013/10/natural-ids-in-hibernate.html Страна cer = s.byNaturalId (Country.class) .using ("code", "NL"). getReference (); er.setCountry (С); сохранить (s, er); }} void save (Session s, Object o) {Transaction t = s.beginTransaction (); попробуй {s.save (o); t.commit (); } finally {if (t.isActive ()) {t.rollback (); }}} @Entity @Data stati c class ExpenseReport {@Id @GeneratedValue int id; @column Описание строки; @Column String reason; @ManyToOne // Может также напрямую отображать код страны. // { ссылка } Страна страна; } @Entity @Data // https://vladmihalcea.com/the-best-way-to-map-a-naturalid-business-key-with-jpa-and-hibernate/ @ org.hibernate.annotations.Cache (using = CacheConcurrencyStrategy.READ_WRITE) @NaturalIdCache stati c класс Country {@Id @GeneratedValue int id; @NaturalId String code; }}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...