简体   繁体   中英

Spring hibernate CrudRepository make the save method update based on a unique constraint

I understand by default that the CrudRepository.save method inserts and updates based on a primary key.

Consider the following entity

@Entity
public class BookEntity {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;

    @Basic
    @Column(name = "isbn", unique = true)
    private String isbn;

    @Basic
    @Column(name = "title")
    private String title;

    @Basic
    @Column(name = "author")
    private String author;

    @Basic
    @Column(name = "publication_date")
    private Date publicationDate;

    @Basic
    @Column(name = "rank")
    private Integer rank;
}

Because I can not have books with the same isbn in the table and I don't want to worry about generating ids I normally post the following json to an enpoint

{ "isbn": "10932011", "title": "harry", "author": "jk", "publicationDate": "2018-10-10", "rank": 1000 }

Which returns the following with an auto generated id

{ "id": 3, "isbn": "10932011", "title": "harry", "author": "jk", "publicationDate": "2018-10-10T00:00:00.000+0000", "rank": 1000 }

If I make second call using the same isbn I'll get a constraint error

{ "isbn": "10932011", "title": "harry", "author": "jk3", "publicationDate": "2018-10-10", "rank": 1000 }

But I would like to in fact update the book with the same isbn and not have to specify the auto generated id in the post json as this is not important to me. Is there a way of doing this without having to add logic to a service class?

You can get the BookEntity , change it and save it.

Since your isbn is unique, you can do something like this:

BookEntity book = bookRepository.findByIsbn(isbn);
book.setWhateverFieldYouWant(value);
bookRepository.save(book);.

Or another solution

You can create a method with @Query annotation in BookRepository :

@Query("UPDATE BookEntity b SET b.whateverField = :value WHERE b.isbn = :isbn")
void updateBook(@Param("value") String value, @Param("isbn") String isbn);

and call it from service:

bookRepository.updateBook(value, isbn);

Since you are using Hibernate, you can also take a look at the NaturalId API Hibernate provides. In addition to your generated ID you annotate your isbn as a @NaturalId and then use the NaturalId API to retrieve your books.

@Entity
public class BookEntity {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;

    @NaturalId
    @Column(name = "isbn")
    private String isbn;

Load example:

BookEntity book = entityManager.unwrap(Session.class)
.bySimpleNaturalId(BookEntity.class)
.load("your isbn goes here");

For further reading on NaturalIds take a look at this article (its sample is with isbns) or this one .

A big plus of NaturalIds is you can benefit of hibernate caching mechanisms.

The best way I have found is based in this article using @NaturalId :

It works by replacing the CrudRepository by JpaRepository ... but wait, is not that simple, but it is not that hard. Just follow these steps:

1. Create a NaturalRepository interface:

@NoRepositoryBean
interface NaturalRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
    Optional<T> findBySimpleNaturalId(ID naturalId);
}

2. Create an implementation of such interface:

@Transactional(readOnly = true)
class NaturalRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements NaturalRepository<T, ID> {
    final EntityManager entityManager;

    NaturalRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
    }

    @Override
    Optional<T> findBySimpleNaturalId(ID naturalId) {
        Optional<T> entity = entityManager.unwrap(Session.class)
                .bySimpleNaturalId(this.getDomainClass())
                .loadOptional(naturalId);
        return entity;
    }
}

3. Enable it in your Spring App:

@SpringBootApplication
@EnableJpaRepositories(repositoryBaseClass = NaturalRepositoryImpl.class)
class MySpringApp {
...

4. Annotate with @NaturalId your field:

@Entity
public class BookEntity {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;

    @NaturalId
    @Column(name = "isbn")
    private String isbn;

5. Convert your CrudRepository interface:

@Repository
interface BookRepository extends NaturalRepository<Book, String> {
    // You can add other methods here like:
    List<Book> findByTitle(String title);
    List<Book> findByAuthor(String author);
}

Be sure:

  • its now using @Repository (instead of @RepositoryRestResource )
  • that it should now extends NaturalRepository (instead of CrudRepository)
  • the second type should be String

6. Finally, you can use it as:

...
@Autowired
BookRepository repository;
...
String isbn = "...";
try {
    Book book = repository.findBySimpleNaturalId(isbn).get();
    book.author = "gal";
    repository.save(book);
} catch(NoSuchElementException notfound) {
    ...
}

The nice thing is that you can reuse the NaturalRepository and NaturalRepositoryImpl in any other project or with any other repository for other tables.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM