I'm creating a delete api endpoint for my spring boot application. I tried using the delete() and deleteById() methods provided by the JpaRepository. However, whenever I try to delete a concert, using the ConcertEntity or the concertId, the venue entry associated is deleted from the Venues table. How do I prevent deleting reference entities/tables using the JpaRepository?
My current solution is to set the venue to null before deleting the concert entity. My concertRepositroy extends to JpaRepository.
Current Solution in Service Impl
public void deleteConcert(ConcertEntity e){
e.setVenue(null);
this.concertRepository.delete(e);
}
Concert Entity
#Entity
#Table(name = "CONCERTS")
public class ConcertEntity{
#Id
private UUID concertId;
#Column(name = "ARTIST")
String artist;
#Column(name = "VENUE_ID")
VenueEntity venue;
/*Getters && Setters here...*/
}
Use the proper annotation to define the relationship (#ManyToOne or #OneToOne)
#ManyToOne(optional = true)
#JoinColumn(name = "VENUE_ID")
private VenueEntity venue;
That should not trigger any cascade deletion by default, but you can add the cascade parameter to the #ManyToOne or #OneToOne annotation if you want to customize the behavior.
Related
I have 2 entities:
#Data
#Entity
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Table(name = "source_company")
public class SourceCompany {
#Id
#EqualsAndHashCode.Include
private UUID id;
private String name;
#OneToMany( mappedBy = "company")
private final Set<SourceUser> users = new HashSet<>();
#Column(name = "version")
#Version
private Long version;
}
#Data
#Entity
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Table(name = "source_user")
public class SourceUser {
#Id
#EqualsAndHashCode.Include
private UUID id;
private String name;
#Column(name = "version")
#Version
private Long version;
//ref
#ManyToOne
#JoinColumn(name = "fk_source_company")
private SourceCompany company;
}
Is it correct to save in this way (only 2 save)?
#Test
public void testSourceUserSave() {
SourceCompany sourceCompany= new SourceCompany();
sourceCompany.setName("xxx");
sourceCompany.setId(UUID.fromString("2bf05cbc-d530-11eb-b8bc-0242ac130003"));
SourceUser sourceUser= new SourceUser();
sourceUser.setName("dev-team");
sourceUser.setId(UUID.fromString("4bede7a0-d530-11eb-b8bc-0242ac130003"));
sourceUser.setCompany(sourceCompany);
sourceCompany.getUsers().add(sourceUser);
sourceCompanyRepository.save(sourceCompany);
sourceUserRepository.save(sourceUser);
assertNotNull(sourceUser);
assertEquals(sourceUser.getCompany().getId(), sourceCompany.getId());
assertEquals(sourceCompany.getUsers().stream().findFirst().get().getId(), sourceUser.getId());
}
or I need to save the user (without company) and the company (without user) and after that to update the user with a save and the company (without save because is not the owner) like this (3 save):
#Test
public void testSourceUserSave() {
SourceCompany sourceCompany= new SourceCompany();
sourceCompany.setName("xxx");
sourceCompany.setId(UUID.fromString("2bf05cbc-d530-11eb-b8bc-0242ac130003"));
SourceUser sourceUser= new SourceUser();
sourceUser.setName("dev-team");
sourceUser.setId(UUID.fromString("4bede7a0-d530-11eb-b8bc-0242ac130003"));
sourceUserRepository.save(sourceUser);
sourceCompanyRepository.save(sourceCompany);
sourceUser.setCompany(sourceCompany);
sourceCompany.getUsers().add(sourceUser);
sourceUserRepository.save(sourceUser);
assertNotNull(sourceUser);
assertEquals(sourceUser.getCompany().getId(), sourceCompany.getId());
assertEquals(sourceCompany.getUsers().stream().findFirst().get().getId(), sourceUser.getId());
}
It seems, looking in the db, that the first way works, so in future can I update only the owner side (I mean update and save) and so can I update the not-owner side only in the object without save it again?
Thanks in advance
You usually tend to save only one of the objects. This can be done adding the
#ManyToOne(cascade = CascadeType.PERSIST)
to the mapping annotation. This makes sure that the nested entities get persisted too
You would need to do just:
SourceCompany sourceCompany= new SourceCompany();
sourceCompany.setName("xxx");
sourceCompany.setId(UUID.fromString("2bf05cbc-d530-11eb-b8bc-0242ac130003"));
SourceUser sourceUser= new SourceUser();
sourceUser.setName("dev-team");
sourceUser.setId(UUID.fromString("4bede7a0-d530-11eb-b8bc-0242ac130003"));
sourceUser.setCompany(sourceCompany);
sourceUserRepository.save(sourceUser);
One more thing to note is that the .save method actually returns an entity itself. That entity is the persisted entity just created. Basically if you manage everything within a single transactional method any modification to the persisted entity within that method (transaction) will be applied without calling any save, merge or update method
I suggest reading about the #Transactional annotation
I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.
I am writing a Spring Boot application that will use Hibernate/JPA to persist between the app and a MySQL DB.
Here we have the following JPA entities:
#MappedSuperclass
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private Long id;
#Type(type="uuid-binary")
private UUID refId;
}
#Entity(name = "contacts")
#AttributeOverrides({
#AttributeOverride(name = "id", column=#Column(name="contact_id")),
#AttributeOverride(name = "refId", column=#Column(name="contact_ref_id"))
})
public class Contact extends BaseEntity {
#Column(name = "contact_given_name")
private String givenName;
#Column(name = "contact_surname")
private String surname;
#Column(name = "contact_phone_number")
private String phone;
}
#Entity(name = "assets")
#AttributeOverrides({
#AttributeOverride(name = "id", column=#Column(name="asset_id")),
#AttributeOverride(name = "refId", column=#Column(name="asset_ref_id"))
})
public class Asset extends BaseEntity {
#Column(name = "asset_location")
private String location;
}
#Entity(name = "accounts")
#AttributeOverrides({
#AttributeOverride(name = "id", column=#Column(name="account_id")),
#AttributeOverride(name = "refId", column=#Column(name="account_ref_id"))
})
public class Account extends BaseEntity {
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "contact_id", referencedColumnName = "contact_id")
private Contact contact;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "asset_id", referencedColumnName = "asset_id")
private Asset asset;
#Column(name = "account_code")
private String code;
}
And the #RestController, where an Account instance will be POSTed (to be created):
public interface AccountRepository extends CrudRepository<Account, Long> {
#Query("FROM accounts where account_code = :accountCode")
public Account findByCode(#Param("accountCode") String accountCode);
}
#RestController
#RequestMapping(value = "/accounts")
public class AccountController {
#Autowired
private AccountRepository accountRepository;
#RequestMapping(method = RequestMethod.POST)
public void createNewAccount(#RequestBody Account account) {
// Do some stuff maybe
accountRepository.save(account);
}
}
So the idea here is that "Account JSON" will be sent to this controller where it will be deserialized into an Account instance and (somehow) persisted to the backing MySQL. My concern is this: Account is a composition (via foreign keys) of several other entities. Do I need to:
Either create CrudRepository impls for each of these entities, and then orchestrate save(...) calls to those repositories such that the "inner-entitities" get saved first before the "outer" Account entity?; or
Do I just save the Account entity (via AccountRepository.save(account)) and Hibernate/JPA automagically takes care of creating all the inner/dependendent entities for me?
What would the code/solution look like in either scenario? And how do we specify values for BaseEntity#id when it is an auto-incrementing PK in the DB?
That depends on your design and specific use cases, and what level of flexibility you want to keep. Both ways are used in practice.
In most CRUD situations, you would rather save the account and let Hibernate save the entire graph (the second option). Here you usually have another case which you didn't mention, and it is updating of the graph, which you would probably do the same way, and actually the Spring's repository save method does it: if the entity is a new (transient) one, it persists it, otherwise it merges it.
All you need to do is to tell Hibernate to cascade the desired entity lifecycle operations from the Account to the related entities:
#Entity
...
public class Account extends ... {
#OneToOne(..., cascade = {CascadeType.PERSIST, CascadeType.MERGE})
...
private Contact contact;
#OneToOne(..., cascade = {CascadeType.PERSIST, CascadeType.MERGE})
...
private Asset asset;
...
}
However, you pay the penalty of reloading the object graph from the db in case of merge operation, but if you want everything done automatically, Hibernate has no other way to check what has actually changed, other than comparing it with the current state in the db.
Cascade operations are applied always, so if you want more flexibility, you obviously have to take care of things manually. In that case, you would omit cascade options (which is your current code), and save and update the parts of the object graph manually in the order that does not break any integrity constraints.
While involving some boilerplate code, manual approach gives you flexibility in more complex or performance-demanding situations, like when you don't want to load or reinitialize the parts of the detached graph for which you know that they are not changed in some context in which you save it.
For example, let's assume a case where there are separate web service methods for updating account, contact and asset. In the case of the account method, with cascading options you would need to load the entire account graph just to merge the changes on the account itself, although contact and asset are not changed (or worse, depending on how you do it, you may here revert changes on them made by somebody else in their dedicated methods in the meantime if you just use the detached instances contained in the account).
Regarding auto-generated ids, you don't have to specify them yourself, just take them from the saved entities (Hibernate will set it there). It is important to take the result of the repository's save method if you plan to use the updated entity afterwards, because merge operation always returns the merged copy of the passed-in instance, and if there are any newly persisted associated entity instances in the updated detached graph, their ids will be set in the copy, and the original instances are not modified.
I have this scenario:
public abstract class AbstractEntity {
#Id #GeneratedValue(strategy = GenerationType.TABLE)
protected Long id;
}
public class User extends AbstractEntity {
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private Set<Dependent> dependents;
}
public class Dependent extends AbstractEntity {
#ManyToOne
#JoinColumn
private User user;
}
When I try to insert() a new User instance with some Dependent's that already are present in database, what means they have the id field populated, I get a Detached entity passed to persist exception.
It can be solved by manually loading all the Dependents from database, but it don't look right.
I would like to know how to make JPA automatically load them when id field is populated. How can I do that?
If you are using JPA API use:
EntityManager.merge(T t)
instead of
EntityManager.persist(Object object)
Since you are using CascadeType.ALL, when you use merge, the JPA Provider will try to update the Users (in case they exists in database) or will create new ones.
Documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html.
I am using Hibernate and JPA. If I have two simple entities:
#Entity
#Table(name = "container")
public class Container {
#Id
#Column(name="guid")
private String guid;
}
#Entity
#Table(name="item")
public class Item {
#Id
#Column(name="guid")
private String guid;
#Column(name="container_guid")
private String containerGuid;
}
and I want to insure that inserting an Item fails if the referenced Container does not exist. I would prefer not to have a Container object populated inside the item object (ManyToOne), how would I do this if it is possible to do?
You can declare arbitrary constraint using columnDefinition attribute:
#Column(name="container_guid",
columnDefinition = "VARCHAR(255) REFERENCES container(guid)")
private String containerGuid;
Note, however, that Hibernate doesn't know anything about this constraint, so that, for example, it may not perform inserts in proper order with respect of it and so on.
Therefore it would be better to create a #ManyToOne relationship. If you are afraid of extra SQL query for Container needed to set this property, you can use Session.load()/EntityManager.getReference() to get a proxy without issuing actulal query.
Try using below relationship mapping
RelationShip Mapping
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#ManyToOne()
#ManyToMany()
<>
#JoinColumn(name="<>")