Reputation: 2742
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?
Upvotes: 5
Views: 4946
Reputation: 25210
The best way I have found is based in this article using @NaturalId:
NaturalRepository
interface (to fine-tune the built-in JpaRepository):@NoRepositoryBean
interface NaturalRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
Optional<T> findBySimpleNaturalId(ID naturalId);
}
NaturalRepository
(to customize it):import org.hibernate.Session;
@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) {
return entityManager.unwrap(Session.class)
.bySimpleNaturalId(this.getDomainClass())
.loadOptional(naturalId);
}
}
@SpringBootApplication
@EnableJpaRepositories(repositoryBaseClass = NaturalRepositoryImpl.class)
class MySpringApp {
...
@NaturalId
:@Entity
public class BookEntity {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@NaturalId
@Column(name = "isbn")
private String isbn;
@Repository
interface BookRepository extends NaturalRepository<Book, String> {
// You can add other methods here like:
List<Book> findByTitle(String title);
List<Book> findByAuthor(String author);
}
Take note:
@Repository
(instead of @RepositoryRestResource
)NaturalRepository
(instead of CrudRepository)String
...
@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.
Upvotes: 1
Reputation: 937
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.
Upvotes: 1
Reputation: 7340
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);
Upvotes: 2