Hibernate querying the entity after fetching its collection - java

We have the following 2 classes
public class StagingConcept implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence")
#SequenceGenerator(name = "sequence", sequenceName = "stg_concept_seq")
#Column(name = "id")
private long id;
#Column(name = "concept_id", nullable = false, length = 18)
private String conceptId;
#OneToMany(mappedBy = "concept", fetch = FetchType.LAZY,
cascade = { CascadeType.ALL })
private Set<StagingConceptDescription> descriptions;
// rest of the class
}
public class StagingConceptDescription {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence")
#SequenceGenerator(name = "sequence", sequenceName = "stg_concept_desc_seq")
#Column(name = "id")
private long id;
#ManyToOne
#JoinColumn(name = "concept_id", referencedColumnName = "concept_id")
#ForeignKey(name = "stg_concept_desc_fk1")
private StagingConcept concept;
// rest of the class
}
Some of the details, such as other class properties and entity annotations, have been omitted to keep the example precise. Please let me know if you need more details. Yes, the FK from StagingConceptDescription to StagingConcept is a non-PK Foreign Key.
When I create a Criteria:
"from " + StagingConcept.class.getCanonicalName()
I get all the StagingConcept entities from the DB in one single query. But I need to get the descriptions for each StagingConcept. For that, I write a query:
"from " + StagingConcept.class.getCanonicalName() + " join fetch descriptions"
The resulting SQL looks like:
select stagingcon0_.id as col_0_0_,
descriptio1_.id as id178_1_,
stagingcon0_.is_active as is2_149_0_,
stagingcon0_.brand_restriction_status as brand3_149_0_,
stagingcon0_.concept_id as concept4_149_0_,
stagingcon0_.container_type as container5_149_0_,
stagingcon0_.controlled_drug_status as controlled6_149_0_,
stagingcon0_.effective_date as effective7_149_0_,
stagingcon0_.form as form149_0_,
stagingcon0_.is_multi_component as is9_149_0_,
stagingcon0_.namespace as namespace149_0_,
stagingcon0_.preferred_term as preferred11_149_0_,
stagingcon0_.source as source149_0_,
stagingcon0_.source_version as source13_149_0_,
stagingcon0_.subsidy_status as subsidy14_149_0_,
stagingcon0_.type as type149_0_,
stagingcon0_.unit_of_use_size as unit16_149_0_,
stagingcon0_.unit_of_use_size_unit as unit17_149_0_,
descriptio1_.is_active as is2_178_1_,
descriptio1_.concept_id as concept6_178_1_,
descriptio1_.is_preferred as is3_178_1_,
descriptio1_.term as term178_1_,
descriptio1_.type as type178_1_,
descriptio1_.concept_id as concept6_149_0__,
descriptio1_.id as id0__
from stg_concept stagingcon0_
inner join stg_concept_description descriptio1_ on stagingcon0_.concept_id=descriptio1_.concept_id
It does fetch all the StagingConcepts and their descriptions, albeit in a slightly larger result set in that SQL.
All looks fine up until here. But then it goes and tries to find a staging concept for each and every description again. So if I have 30000 Staging Concepts and 60000 descriptions, it will send another 60000 queries to fetch the staging concept for every description. This looks a little nasty and takes up huge amount of time, enough to run past the transaction timeout.
To attempt to resolve this issue, I change the StagingConceptDescription to
public class StagingConceptDescription {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence")
#SequenceGenerator(name = "sequence", sequenceName = "stg_concept_desc_seq")
#Column(name = "id")
private long id;
#ManyToOne(fetch = FetchType.LAZY, optional=false)
#JoinColumn(name = "concept_id", referencedColumnName = "concept_id")
#ForeignKey(name = "stg_concept_desc_fk1")
private StagingConcept concept;
// rest of the class
}
So, the ManyToOne relationship is now set to LAZY, explicitly. And also, the relationship states that the concept is not optional, in an attempt to indicate that the relationship is non-optional. By setting that, I meant to tell hibernate that it should be OK to create a proxy object, if needed, because the other end of the relationship is always going to be present. But none of this had any effect. Not too sure if this would work.
I have also tried the #Fetch annotations, etc. None of them work. Even setting it to #LazyToOne(LazyToOneOption.PROXY) didn't have any effect.

Based off https://stackoverflow.com/a/29863982/5464931, you can do another join fetch.
e.g.
"from " + StagingConcept.class.getCanonicalName() + " join fetch descriptions join fetch descriptions.concept"
But this isn't the best solution because it still queries the entity/parent/concept again which is unnecessary.

Related

hibernate search 6 updating elasticsearch index after entity update

I have a problem using elasticsearch with hibernate search 6. Let's assume we have this setup :
#Entity
#Table(name = "entityA")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#Indexed(index = "entityA")
public class EntityA {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#GenericField
private Long id;
#Column(name = "name")
#KeywordField
private String name;
#OneToOne
#JoinColumn(unique = true)
#Cascade(value = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.SAVE_UPDATE})
#IndexedEmbedded
#IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW)
private EntityB entityB;
}
#Entity
#Table(name = "entityB")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class EntityB {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#GenericField
private Long id;
#Column(name = "name")
#KeywordField
private String name;
#OneToOne(cascade = {}, fetch = FetchType.EAGER, targetEntity = EntityA.class)
#JoinColumn(name = "id", nullable = false)
#IndexingDependency(reindexOnUpdate = ReindexOnUpdate.DEFAULT)
private EntityA entityA
}
When I first persist EntityA, that being the entity that is indexed, the EntityB is persisted in the elasticsearch index as a child of EntityA. This is ok. The problem appears when I directly edit EntityB and make changes to it, this changes are not propagated to the elasticsearch index. Is something that i am missing?
UPDATE 1
After #yrodiere answers, i made this changes :
#OneToOne
#JoinColumn(unique = true)
#Cascade(value = {CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.SAVE_UPDATE})
#IndexedEmbedded
#AssociationInverseSide(inversePath = #ObjectPath(
#PropertyValue(
propertyName = "entitya" ) ))
private EntityB entityB;
The problem still persist. If i do something like this :
EntityB b = entityBRepository.findById(5051L).get();
b.setProperty("3333");
entityBRepository.save(b);
Regards.
The problem appears when I directly edit EntityB and make changes to it, this changes are not propagated to the elasticsearch index.
You explicitly instructed Hibernate Search to behave exactly that way:
#IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW)
private EntityB entityB;
reindexOnUpdate = ReindexOnUpdate.SHALLOW means "reindex EntityA when the entityB property of EntityA changes, but not when a property of EntityB itself (e.g. its name) changes".
See this section of the reference documentation.
I'm guessing you added that to get rid of an exception telling you that Hibernate Search was unable to find the inverse side of the association EntityA.entityB. In your case, it seems you should rather tell Hibernate Search what the inverse side of that association is. Either add a mappedBy to one side of the association (Warning: this will change your DB schema), or use #AssociationInverseSide (see this section of the documentation).

Get recently updated results from Database when joining multiple tables using JPA in a Spring application

I am new to Spring and JPA and I am trying to write a job in Spring which runs every 3 hours and retrieve the records from Oracle Database.
I would like to only read the new/updated content from the past 3 hours (ideally from the last job run).
I have seen few examples in https://spring.io/blog/2011/02/10/getting-started-with-spring-data-jpa/ where we can create queries and retrieve the data based on our requirements, but in my current use case, I am not using queries instead using the java classes with the annotations and using Join columns between different tables. There are chances that only one of the sub table is updated or all the tables are updated with new content. I need to get the results if at least one of the table is updated/inserted with new content.
Campus is the main table and retrieves the data from Group and Region, I need to fetch the data if any new data is updated in Campus table or even any group/region is added/modified.
I am using JDK7 as of now.
Is there a way to accomplish the above requirement?
Below are the sample Java classes for reference.
#Entity
#EntityListeners(AuditListener.class)
#Table(name = "TABLE_CAMPUS")
public class Campus implements Auditable {
#Id
#Column(name = "ID)
#SequenceGenerator(name = "SIMPLE_ID", sequenceName = "SIMPLE_ID")
#GeneratedValue(generator = "SIMPLE_ID", strategy = GenerationType.AUTO)
private Long id;
#Column(name = "CAMPUS_NAME")
private String campusName;
#Column(name = "CAMPUS_ID", nullable = false)
private Long campusId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "GROUP_ID")
private GroupType groupType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "REGION_ID")
private Region region;
....
...
}
#Entity
#EntityListeners(AuditListener.class)
#Table(name = "TABLE_GROUP_TYPE")
public class GroupType implements Auditable {
#Id
#Column(name = "GROUP_TYPE_ID")
#SequenceGenerator(name = "GROUP_TYPE_SEQUENCE", sequenceName = "GROUP_TYPE_ID")
#GeneratedValue(generator = "GROUP_TYPE_SEQUENCE", strategy = GenerationType.AUTO)
protected Long id;
#Column(name = "GROUP_TYPE_NAME", nullable = false)
protected String groupTypeName;
....
...
}
#Entity
#EntityListeners(AuditListener.class)
#Table(name = "TABLE_REGION")
public class Region implements Auditable {
#Id
#Column(name = "region_id")
#SequenceGenerator(name = "REGION_ID", sequenceName = "REGION_ID")
#GeneratedValue(generator = "REGION_ID", strategy = GenerationType.AUTO)
private Long id;
#Column(name = "REGION_NAME", nullable = false)
private String name;
...
..
}
Any help is Appreciated.

Java Spring Boot - JPA : StackOverflowError with a #ManyToMany relation

I am currently developing an application with the API in JavaSpringBoot and I need to add relationships between users (friends). For this I made a many-to-many relation which contains two fields:
CREATE TABLE friend_relation
(
fk_id_friend integer REFERENCES users (id) NOT NULL,
fk_id_user integer REFERENCES users (id) NOT NULL,
PRIMARY KEY (fk_id_friend, fk_id_user)
);
When I connect with a user and add relationships : everything is fine, but if I connect to one of the accounts added as a friend by the other user, there is a StackOverflowError.
I know it's because of the almost identical entries in the database, but I have no idea how to get my entity to be correct.
Currently each user must add the other individually, I imagine that I have to make a friend request system but again I am blocked.
Do I have to make an "effective" field in my friend_relation table. If so, how do I use it? Should I create a specific entity for this table or leave it in the User entity?
Currently, this is what my user entity looks like:
#Entity
#Table(name = "users")
#Data
#Accessors(chain = true)
public class UserEntity {
#Id
#SequenceGenerator(name = "users_generator", sequenceName = "users_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_generator")
#Column(name = "id")
private Integer id;
[...]
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "friend_relation",
joinColumns = #JoinColumn(name = "fk_id_user", referencedColumnName = "id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "fk_id_friend", referencedColumnName = "id", nullable = false)
)
private List<UserEntity> friends = new ArrayList<>();
}
When trying to modify my entity to avoid the error:
#ManyToMany
#JoinTable(name="friend_relation",
joinColumns=#JoinColumn(name="fk_id_user"),
inverseJoinColumns=#JoinColumn(name="fk_id_friend")
)
private List<UserEntity> friends;
#ManyToMany
#JoinTable(name="friend_relation",
joinColumns=#JoinColumn(name="fk_id_friend"),
inverseJoinColumns=#JoinColumn(name="fk_id_user")
)
private List<UserEntity> friendOf;
I looked for resources on the internet but I did not find it, I may have searched poorly and I apologize in advance if the answer has already been given.
If you can help me it is with great pleasure, thank you in advance!
(And sorry for the Google Translate I preferred not to use my rough English)✌
Ok sorry update, i don't post the stake trace : https://pastebin.com/Ls2qRpU4
It happens when I get my user on the front side. I am trying to connect but I cannot because this error occurs.
First of, I've noticed an #Data on your entity, which I suppose is from Project Lombok? Just be careful, Lombok generated methods can trigger lazy loading and there are problems with equals/hashCode for auto-generated IDs. (see here and here)
Now to your problem:
It's indeed a JSON serialization issue. You have circular references and Jackson runs in circles bc. it doesn't know where to stop.
You can either use a DTO projection where you resolve this circle yourself, like:
public class UserDto {
public Integer id;
public List<Integer> friends; // List of IDs
}
You can also use Jackson annotations to achieve almost the same thing, like #JsonIdentityInfo. Here's an article about it.
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#Entity
#Table(name = "users")
#Data
#Accessors(chain = true)
public class UserEntity {
#Id
#SequenceGenerator(name = "users_generator", sequenceName = "users_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_generator")
#Column(name = "id")
private Integer id;
[...]
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "friend_relation",
joinColumns = #JoinColumn(name = "fk_id_user", referencedColumnName = "id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "fk_id_friend", referencedColumnName = "id", nullable = false)
)
private List<UserEntity> friends = new ArrayList<>();
}
When you have complex Entities with circular references or very large object trees, you often have to think hard about how to serialize them - especially when taking LazyLoading into account. I have spent a lot of time on this in complex professional projects. Simple automatically generated DTOs and serialization sometimes don't cut it.
I'm not an expert on this, so I'm sure others will give more accurate answers. However, the problem seems to occur when mapping the entity to JSON. You can't map to JSON a user with a collection of friend users that also have their friends in a fully mapped collection since that causes infinite recursion. I would try to use Jackson annotations to make the serialization of the friends list produce a list of ids instead of a list of complete users.

JPA #MappedSuperclass different Id counters for subclasses

I've got a #MappedSuperclass which is my base-class for all my entities (#Entity, direct or indirect through multiple sub-classing).
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#XmlAttribute(required = true)
private Long primaryKey;
The Id is generated like shown above.
My problem is that the #Id-counter is the same for each #Entity. In fact thats not a big problem because it would take a while to reach the Long.MAX_VALUE. But reaching that maximum value is a lot easier because there is only one counter for all entities. How can I use a different #Id-counter without having to add the above code to all #Entity-classes?
(If it matters for your answer: I'm using a H2-Database.)
If your database and tables support AUTO_INCREMENT change the annotation to this #Id #GeneratedValue(strategy=GenerationType.IDENTITY). Then the id will be generated during commit.
There is another way via TABLE or SEQUENCE strategy, but it needs to be explicitly defined per Entity which is problem for abstract BaseEntity. Just hava a look:
#Entity
#TableGenerator(name="tab", initialValue=0, allocationSize=50)
public class EntityWithTableId {
#GeneratedValue(strategy=GenerationType.TABLE, generator="tab")
#Id long id;
}
EDIT:
Well, so it is possible!
MappedSuperclass - Change SequenceGenerator in Subclass
#Id
#Column(name = "ID", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.TABLE, generator = "SEQ_AAAAAAAAAAA")
#TableGenerator(
name = "SEQ_AAAAAAAAAAA",
table = "SEQ_ENTITY" /*<<<==YOUR TABLE NAME FOR SAVE NEXT VALUES HERE*/,
pkColumnName = "ENTITY",
initialValue = 1,
valueColumnName = "NEXT_ID",
pkColumnValue = "packageee.PACK.PAK.YOURCLASSNAME",
allocationSize = 1)
private Long id;

JPA #ManyToMany ordering failure using #OrderBy

When JPA tries to select AdmUser entity I have sql error:
ERROR: column locations1_.name does not exist.
Is there anything wrong with my entities? My AdmUser entity:
#Entity
#Table(name = "ADM_USERS")
#SequenceGenerator(name = "ADM_USER_SEQ", sequenceName = "ADM_USER_SEQ", allocationSize = 1)
public class AdmUser implements EntityInt, Serializable {
private static final long serialVersionUID = 786L;
#Id
#Column(nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ADM_USER_SEQ")
private Long id;
(...)
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JoinTable(name = "loc_locations_adm_users", joinColumns = #JoinColumn(name = "id_user", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name = "id_location"))
#OrderBy("name")
private Set<LocLocation> locations;
(...)
}
My LocLocation Entity:
#Entity
#Table(name = "loc_locations", schema = "public")
#SequenceGenerator(name = "LOC_LOCATIONS_SEQ", sequenceName = "LOC_LOCATIONS_SEQ", allocationSize = 1)
public class LocLocation implements EntityInt, java.io.Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "LOC_LOCATIONS_SEQ")
private Long id;
#Column(nullable = false, unique = true, length = 200)
private String name;
(...)
#ManyToMany(cascade = CascadeType.REFRESH, fetch = FetchType.LAZY, mappedBy="locations")
private List<AdmUser> users;
}
And now - when JPA tries to select AdmUser entity I have sql error. The query generated by JPA looks that:
select
admuser0_.id as id1_2_0_,
admuser0_.actived as actived2_2_0_,
admuser0_.admin as admin3_2_0_,
admuser0_.allow_ip as allow_ip4_2_0_,
admuser0_.created as created5_2_0_,
admuser0_.deleted as deleted6_2_0_,
admuser0_.id_domain as id_doma16_2_0_,
admuser0_.email as email7_2_0_,
admuser0_.language as language8_2_0_,
admuser0_.login as login9_2_0_,
admuser0_.name as name10_2_0_,
admuser0_.passwd as passwd11_2_0_,
admuser0_.phone as phone12_2_0_,
admuser0_.picture as picture13_2_0_,
admuser0_.surname as surname14_2_0_,
admuser0_.theme as theme15_2_0_,
locations1_.id_user as id_user1_2_1_,
loclocatio2_.id as id_locat2_6_1_,
loclocatio2_.id as id1_17_2_,
loclocatio2_.description as descript2_17_2_,
loclocatio2_.name as name3_17_2_
from
public.ADM_USERS admuser0_
left outer join
public.loc_locations_adm_users locations1_
on admuser0_.id=locations1_.id_user
left outer join
public.loc_locations loclocatio2_
on locations1_.id_location=loclocatio2_.id
where
admuser0_.id=1
order by
locations1_.name
The order by points to locations1_.name, but should be loclocatio2_.name. Have I anything wrong with my entities?
You have a Set on one side for that field. Consequently there is no "ordering" (other than what hashCode() gves). Use a List if you want ordering (this is Java, nothing to do with JPA really).
You also seem to be missing a "mappedBy" on the non-owner side of that M-N.
The #OrderBy works fine with ManyToMany. Also with the structure I provided in my question. The problem was my query and JPA didn't managed with it. Sorry.

Categories