When deleting an #Embeddable object, I run into some problems.
I have the following domain classes: SwitchVoipTrunkGroup and PrioritizedCodec. The latter contains several fields that are nullable.
class SwitchVoipTrunkGroup {
//...
#CollectionOfElements(fetch = FetchType.LAZY)
#JoinTable(
name = "SWITCH_VOIP_TKG_CODEC",
joinColumns = #JoinColumn(name = "FK_SWITCH_VOIP_TKG_ID")
)
#ForeignKey(name = "FK_CODEC_SWITCH_VOIP_TKG")
private Set<PrioritizedCodec> prioritizedCodecs = new HashSet<PrioritizedCodec>();
//...
}
#Embeddable
public class PrioritizedCodec {
#Column(name = "PRIORITY")
private String priority;
#Column(name = "FAX_MODE")
private String faxMode;
//... some more columns ...
}
When I edit SwitchVoipTrunkGroup's prioritizedCodecs field (e.g. by deleting an entry) and save the entity, I see the following in my Hibernate logging:
13:54:31,919 INFO [STDOUT] Hibernate: delete from T_SWITCH_VOIP_TKG_CODEC where
fk_switch_voip_tkg_id=? and fax_mode=? and priority=?
From this question I understand why Hibernate uses all the fields in the where clause. However, this gives problems: in case some of these fields are empty, the query will look like so:
delete from T_SWITCH_VOIP_TKG_CODEC where fk_switch_voip_tkg_id=1 and fax_mode = ''
and priority =''
This will however not delete any records, as what is really necessary is for Hibernate to check for NULL iso for an empty string. For example:
delete from T_SWITCH_VOIP_TKG_CODEC where fk_switch_voip_tkg_id=1 and fax_mode
IS NULL and priority IS NULL
(cf. here for more info on why checking for an empty string does not suffice)
Any ideas on how to tackle this? Many thx!
I suggest to normalize your database, so both your classes become entities, and then to setup One-to-Many relation between SwitchVoipTrunkGroup and PrioritizedCodec, then you may setup cascading rules so Hibernate automatically updates collection of elements of PrioritizedCodec type, when you persist instance of SwitchVoipTrungGroup.
#Entity
class SwitchVoipTrunkGroup {
//...
#OneToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST}, orphanRemoval = true)
#JoinColumn(name = "switchVoipTrunkGroup_id")
#ForeignKey(name = "FK_PrioritizedCodec_SwitchVoipTrunkGroup")
private Set<PrioritizedCodec> prioritizedCodecs = new HashSet<PrioritizedCodec>();
//...
}
#Entity
public class PrioritizedCodec {
#Column(name = "PRIORITY")
private String priority;
#Column(name = "FAX_MODE")
private String faxMode;
//... some more columns ...
}
#Serice("someService")
public class SomeService {
#Autowired
private SwitchVoipTrunkGroupDao trunkDao;
public SwitchVoipTrunkGroup doOperation("criteria") {
SwitchVoipTrunkGroup tg = trunkDao.find("criteroa");
tg.getPrioritizedCodecs().[remove(2)]; //remove should be implemened, that is just lame statement
tg.getPrioritizedCodecs().get(5).setFaxMod("ENABLED");
return trunkDao.save(tg); //hibernate will remove missing elements from PrioritizedCodec table, and will update necessary entities.
}
}
Alternatively, you may specify default values for priority and faxMode fields via attributes of #Column annotation and enforce nullable constraints
#Column(columnDefinition = "VARCHAR(20) default 'NONE'", nullable = false)
private String faxMode;
Related
In a simple Spring Boot Application, I'm facing with a JpaObjectRetrievalFailureException when I'm trying to save an entity with one-to-many association and client-assigned ids.
Please take a look on these entities and on this simple repository:
#Entity
#Table(name = "cart")
public class Cart {
#Id
#Column(name = "id")
private UUID id;
#Column(name = "name")
private String name;
#OneToMany
#JoinColumn(name = "cart_id")
private List<Item> items;
// constructors, getters, setters, equals and hashCode ommitted
}
#Entity
#Table(name = "item")
public class Item {
#Id
#Column(name = "id")
private UUID id;
#Column(name = "name")
private String name;
// constructors, getters, setters, equals and hashCode ommitted
}
public interface CartRepository extends JpaRepository<Cart, UUID> {
}
I wrote this test:
#DataJpaTest
class CartRepositoryTest {
#Autowired
private CartRepository cartRepository;
#Test
void should_save_cart() {
// GIVEN
final var cart = new Cart(UUID.randomUUID(), "cart");
final var item = new Item(UUID.randomUUID(), "item");
cart.setItems(List.of(item));
// WHEN
final var saved = cartRepository.save(cart);
// THEN
final var fetched = cartRepository.findById(saved.id());
assertThat(fetched).isPresent();
}
}
When I run the test, call to cartRepository.save(cart) fails with:
Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356
at app//org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:379)
at app//org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:235)
at app//org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551)
at app//org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at app//org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at app//org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at app/jdk.proxy3/jdk.proxy3.$Proxy105.save(Unknown Source)
at app//com.example.testjpaonetomany.repository.CartRepositoryTest.should_save_cart(CartRepositoryTest.java:28)
If I modify my entities by adding #GeneratedValue for ids, and in my test, I replace UUID.randomUUID() by null to delegate to Hibernate the ID generation, the test passes.
How to deal with client-generated ids?
The cause is that you save the parent object only (which is absolutely correct and fine) but still need to explain JPA that the operation should be propagated i.e.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "cart_id")
private List<Item> items;
As minor improvements I would suggest to put the UUID generation into constructors and establish the relation via the dedicated method i.e.
final var cart = new Cart("cart");
cart.addItem(new Item("item"));
and probably consider using em.persist() instead of repository.save() as it makes a select request first in case of using uuids as #Augusto mentioned
I have an entity TeamActivity:
#Entity
#Table(name = "teams_to_activities")
public class TeamActivity {
#Column(name = "scope_id", nullable = false)
private String scopeId;
#Column(name = "team_id", nullable = false)
private String teamId;
#Column(name = "activity_set_id", nullable = false)
private String activitySetId;
#Id
#Column(name = "scoped_team_activity_id", nullable = false)
private String scopedTeamActivityId;
}
And another entity ActivitySet:
#Entity
#Table(name = "activity_sets")
public class ActivitySet {
#Column(name = "scope_id", nullable = false)
private String scopeId;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "description", nullable = false)
private String description;
#Id
#Column(name = "scoped_activity_set_id", nullable = false)
private String scopedActivitySetId;
}
There's no index on any other column besides the PK in both tables.
There's no FK constraint creating a relationship between these tables whatsoever. I have no idea why as this is a legacy system.
Technically, if I fetch a TeamActivity record, I can pick the scope_id and activity_set_id from it and combine them to form a scoped_activity_set_id which would be a valid PK to fetch the corresponding ActivitySet.
I know that TeamActivity -> ActivitySet is a N -> 1 association
I would like to leverage Spring Data JPA features to create an association from TeamActivity to ActivitySet such that when I fetch a TeamActivity from TeamActivityRepository, the corresponding ActivitySet is also returned.
I have created an association like this before using a combination of #JoinColumn and #MapsId but there was actually a single FK to use which is different here where source table has 2 columns I can combine to get the target's key.
If you are fully in control of the database, I may propose you create a Materialized View with the contents you desire from both tables and handle it as any other table with JPA, i.e, create #Entity model and CrudRepository<MVTeamActivitySet, String>.
If you are not fully in control of the database, one easy way to achieve it is to simply create a method that internally executes two lookup queries and retrieves the expected model you want. You will still be using using JPA correctly.
Querying two tables and joining desired fields in the code layer is quite common with denormalized DBs, sometimes you want to avoid the overhead of a Materialized View.
#Override
public TeamActivitySetDto findById(String scopedTeamActivityId) throws DemoCustomException {
Optional<TeamActivity> teamActivityEntity = teamActivityDao.getById(scopedTeamActivityId);
if(teamActivityEntity.isEmpty()) {
throw new DemoCustomException("teamActivity record not found");
}
String scopedActivitySetId =
teamActivityEntity.get().getScopeId() + ":" + teamActivityEntity.get().getActivitySetId();
Optional<ActivitySet> activitySetEntity = activitySetDao.getById(scopedActivitySetId);
if(activitySetEntity.isEmpty()) {
throw new DemoCustomException("activitySet record not found");
}
return TeamActivitySetDto.builder()
.description(activitySetEntity.get().getDescription())
.name(activitySetEntity.get().getName())
.scopedActivitySetId(activitySetEntity.get().getScopedActivitySetId())
.activitySetId(teamActivityEntity.get().getActivitySetId())
.scopedTeamActivityId(teamActivityEntity.get().getScopedTeamActivityId())
.scopeId(teamActivityEntity.get().getScopeId())
.teamId(teamActivityEntity.get().getTeamId())
.build();
}
Lately I encountered a strange phenomenon with my Hibernate/Postgres data. On unknown conditions Hibernate creates orphans of child entities (null foreign key field to parent). But I have annotated the #OneToMany relation as orphanRemoval.
The "main" entity:
#Entity
#Audited
public class Product {
#NotNull(groups = { CheckId.class })
#NotBlank(groups = { CheckId.class })
#Id
#Column(length = 32)
private String id = IdGenerator.createId();
#Version
private Integer version;
#OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, orphanRemoval=true)
#JoinColumn(name=Product.PRODUCT_ID)
#Index(name="idx_prod_ident")
private Set<Identifier> identifiers;
...
}
One child entity:
#Entity
#Audited
public class Identifier {
#NotNull(groups = { CheckId.class })
#NotBlank(groups = { CheckId.class })
#Id
#Column(length = 32)
private String id = IdGenerator.createId();
#Version
private Integer version;
#NotNull
#Column(nullable=false, length=3)
#Index(name = "idx_id_prod_type")
private String type;
#NotNull
#Column(nullable=false, length=65)
#Index(name = "idx_id_prod_value")
private String value;
...
}
There are lots of such #OneToMany relations with different (but structurally similar) entities in Product. Millions of records are written correctly, but in most of them I occasionally encounter some hundreds with product_id null. How is this possible?
Unfortunately I cannot easily determine when this happens (due to the missing product_id). Also product_id is not part of the Envers history table of the child entities. So I cannot examine if the product still exists, and what the services have done with it lately.
For information: when a child entity is removed from the parent, this is done via
product.getIdentifiers.remove(identifier);
or
product.getIdentifiers.removeAll(identifiers);
or
product.getIdentifiers.clear();
This is hopefully a valid way to remove them ;)
If I correctly understand you use FetchType.LAZY - it mean the data will be retrieved only during the session, you can use Hibernate.initialize for retrieving #OneToMany fields or can use FetchType.EAGER instead FetchType.LAZY
I have recently started to refactor my project because I had to add an extra column to some of my table. The extra column is an Enum (Pending, or Active).
Because of that change I would need now to refactor ALL my queries to only retrieves a row if the status is ACTIVE.
After some research I found that we can annotate an Entity with the #Where annotation. it works fine where I use it on a simple column but my table look like this:
#Where(clause = 'state='ACTIVE'")
#Entity
public class Place {
#Column(name="id_place")
private String placeId;
#Column(name="name")
private String palceName;
#OneToMany(mappedBy = "place")
private Set<PlaceTag> placeTag;
...
...
}
#Where(clause = 'state='ACTIVE'")
#Entity
public class Tag {
#Column(name="id_tag")
private String tagId;
#Column(name="name")
private String tagName;
#OneToMany(mappedBy = "tag")
private Set<PlaceTag> placeTag;
...
...
}
#Where(clause = 'poi.state='ACTIVE' AND tag.state='ACTIVE")
#Entity
public class PlaceTag {
#Column(name="id")
private String id;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "place_id")
private Place place;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "tag_id")
private Tag tag;
...
...
}
Now my question would be how can make this statement ONLY return the places and tags that are ACTIVE ?
SELECT pt FROM PlaceTag pt;
Is this possible? Or will I have to write the query Explicitly ?
Thank you
As you already discovered, or simply use cases the #Where clause is just fine, but in your case, you want to filter PlaceTag by the place and tag too, so a joined is required in this situation.
So, you can keep the #Where clause for Place and Tag, while for PlaceTags you need to use a JPQL query:
select pt
from PlaceTag pt
join pt.tag t
join pt.place p
where
t.state='ACTIVE' and p.state='ACTIVE'
At least until #WhereJoinTable annotation is made to work for many-to-one associations too.
I got these 2 entities:
#javax.persistence.Entity
public class Book {
#javax.persistence.EmbeddedId
private BookPK id;
private String title;
#javax.persistence.ManyToOne(fetch = javax.persistence.FetchType.LAZY)
#javax.persistence.JoinColumns({
#javax.persistence.JoinColumn(name = "LNGCOD", referencedColumnName = "LNGCOD"),
#javax.persistence.JoinColumn(name = "LIBCOD", referencedColumnName = "LIBCOD") })
private Language language;
}
#javax.persistence.Entity
public class Language {
#javax.persistence.EmbeddedId
private LanguagePK id;
private String name;
}
with composed PK's:
#Embeddable
public class BookPK implements Serializable {
private Integer bookcod;
private Integer libcod;
}
#Embeddable
public class LanguagePK implements Serializable {
private Integer lngcod;
private Integer libcod;
}
If I try to create a new Book and persist it, I get an exception telling me libcod is found twice in the insert statement ("Column 'libcod' specified twice"). But I can't use "insertable = false" when defining the JoinColumn ("Mixing insertable and non insertable columns in a property is not allowed").
Is there any way to define these objects + relationship so the columns are managed automatically by Hibernate ?
Hibernate and JPA automatically make persistent all the modifications made to persistent entities while they are attached to the session. That's the whole point of an ORM: you load a persistent object, modify it, and the new state is automatically persisted at the commit of the transaction, without any need to call persist, merge, save or any other method.
Note that calling persist on a persistent entities (except for its cascading side-effects) makes no sense. persist is to make a transient entity (i.e. a new one, not in the database yet, with no generated ID) persistent.
You can only have one mutator for the libcod. Probably, what you should do is leave the libcod getter in the BookPK class, and in the Language class, use a joincolumn reference with a ref back to the libcod. It works fine for single embedded PK classes, but for multiple PK classes, you may have to play around.
So, in your Language class, you would have this.
#javax.persistence.Entity public class Language {
private LanguagePK id;
private Integer libcod;
#javax.persistence.EmbeddedId #AttributeOverrides({
#AttributeOverride(name = "lngcod", column = #Column(name = "LNGCOD", nullable = false)),
#AttributeOverride(name = "libcod", column = #Column(name = "LIBCOD", nullable = false)) })
public LanguagePK getId() {
return this.id;
}
public void setId(LanguagePK id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "LIBCOD", insertable = false , updatable = false)
public Integer getLibcod() {
return this.libcod;
}