I am using hibernate's #Any(one to any) annotation in my java code. Below here'a a code snippet:
#Entity
public class A {
//.. few properties
#Any(metaColumn = #Column(name = "type"), fetch = FetchType.EAGER)
#AnyMetaDef(idType = "long", metaType = "string", metaValues = {#MetaValue(targetEntity = Http.class, value = "http")})
#JoinColumn(name = "protocol_id")
#Cascade({org.hibernate.annotations.CascadeType.ALL})
#Fetch(FetchMode.SELECT)
private Protocol protocol;
//more code
}
Now when I update the field using session.update(a) where a is an instance of A, a new record for field protocol is created and the earlier one is not deleted. Desired result was that old record for field protocol gets deleted when I update using session.update(a) and new record gets created . Since I am using cascadeType ALL, why is this not working ?
Try to annotate with #OneToOne or #ManyToOne as needed.
Related
I'm working on a project where Hibernate criterias are still in use.
Here is a quick overview of the entities that we're using :
public class Location {
[...]
#OneToMany(mappedBy = "location")
private Set<EventLocation> eventLocations = new HashSet<>();
public class EventLocation {
[...]
#ManyToOne(optional = false, cascade = CascadeType.PERSIST)
#JoinColumn(name = "event_id")
private Event event;
#ManyToOne(optional = false, cascade = CascadeType.PERSIST)
#JoinColumn(name = "location_id")
private Location location;
public class Event {
[...]
#OneToMany(mappedBy = "event", cascade = CascadeType.ALL)
private Set<EventLocation> locations = new HashSet<>();
#OneToMany(mappedBy = "event", cascade = CascadeType.ALL)
private Set<EventTag> tags = new HashSet<>();
public class EventTag {
[...]
#ManyToOne(optional = false, cascade = CascadeType.PERSIST)
#JoinColumn(name = "event_id")
private Event event;
#ManyToOne(optional = false, cascade = CascadeType.PERSIST)
#JoinColumn(name = "tag_id")
private Tag tag;
public class Tag {
[...]
#OneToMany(mappedBy = "tag")
private Set<EventTag> eventTags = new HashSet<>();
I wonder if we have a way, using Hibernate Criteria API, to retrieve a list of Location which holds a filtered list of Event objects ?
For example, we'd like to have a query that would return a location but the eventLocations would only contains results if eventLocations.event.type = "SAMPLE_TYPE" AND eventLocations.event.tags.id IN (1,2,3).
We tried using this, but the eventLocation set contains objects that should have been filtered out.
var crit = getSession().createCriteria(Location.class, "loc");
crit.createAlias("loc.eventLocations", "eloc");
crit.createAlias("eloc.event", "event");
crit.createAlias("event.tags", "etags");
crit.createAlias("etags.tag", "tag");
crit.add(Restrictions.in("etags.id", Arrays.asList(1, 2, 3)));
crit.add(Restrictions.eq("event.type", "SAMPLE_TYPE"));
var locations = crit.list();
Generated query looks like that when I enable Hibernate statistics :
select
[...]
from
location LOC
inner join
event_location ELOC on LOC.id=ELOC.id_location
inner join
event EVENT ON ELOC.id_event=EVENT.id
inner join
event_tag ETAG ON EVENT.id=ETAG.event_id
inner join
tag TAG ON ETAG.id_tag=TAG.id
where
EVENT.type="SAMPLE_TYPE
AND TAG.id in (
1, 2, 3
)
This request runs fine if I run it using PgAdmin, but when using Hibernate directly, it seems like when I loop through locations.eventLocations, it contains eventlocation for events that should have been filtered out because they don't match the "SAMPLE_TYPE" type.
EDIT
It looks like this post asks for the same sub-filtering feature, but to this date no answer was provided. I faced the same behavior mentioned in Steven Francolla's comment (using direct child query returns only matching children's, but when I try to do child-child filtering I doesn't seems to work).
I'm still linking to this post because it explains the problem we're facing with a lot of added details.
Say I have at least two entities.
#Entity
public class Process {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String name;
#ManyToAny(
metaColumn = #Column(name = "node_type"),
fetch = FetchType.LAZY
)
#AnyMetaDef(
idType = "long", metaType = "string",
metaValues = {
#MetaValue(targetEntity = Milestone.class, value = MILESTONE_DISC),
#MetaValue(targetEntity = Phase.class, value = PHASE_DISC)
}
)
#Cascade({org.hibernate.annotations.CascadeType.ALL})
#JoinTable(
name = "process_nodes",
joinColumns = #JoinColumn(name = "process_id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "node_id", nullable = false)
)
private Collection<ProcessNode> nodes = new ArrayList<>();
...
}
#Entity
#ToString
#DiscriminatorValue(MILESTONE_DISC)
public class Milestone implements ProcessNode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Collection<ResultDefinition> results;
#ToString.Exclude
#ManyToOne(fetch = FetchType.LAZY)
#Transient
private Process process;
...
}
Now I want to use spring data jpa specification to find (all) processes which have a milestone with name "S5".
Note that Milestone is a ProcessNode and there is another Entity called Phase which is also a ProcessNode. These can be contained in the "nodes" collection of my Process Entity.
I tried to write something like this:
public static Specification<Process> hasMilestoneWithName(final String milestoneName) {
return (Specification<Process>) (root, query, criteriaBuilder) -> {
Path<?> namePath = root.join("nodes").get("name");
return criteriaBuilder.equal(namePath, milestoneName);
};
}
This does not work, but throws:
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [nodes] on this ManagedType [com.smatrics.dffs.processservice.model.entities.Process]
I don't really know how to use the API. Examples often refer to a meta-model that would be generated by the IDE or maven, but I really do not want to have any static generated resources. Please help me resolve this with Specification of spring-data-jpa without a generated meta-model.
Also if you could help me write the hql it would be awesome.
Thanks!
I would suggest a simpler alternative, coming from bottom-up:
Load Milestone entities with name=S5: findByName("S5")
Return the Process for each Milestone
Filter out the duplicates
Or you could even save a few SQL queries by returning not the Milestone entity but only the ID of the Process for each Milestone and then load the Process nodes by a list of IDs:
The (native) SQL equivalent would be
select *
from process
where id in (
select process_id
from milestone
where name = 'S5'
)
Regardless of my solution your join does not look completely correct to me but I can't point out what's wrong - maybe there are other methods on the JPA metamodel that return a CollectionJoin? Not sure. Probably it is because #ManyToAny is not JPA standard so the JPA criteria API does not recognize nodes as a valid "joinable" field.
Hi I have a Parent Child tables as below. There are below problems.
I am using Spring Data Repository (org.springframework.data.repository)
Question 1
While I am Persisting the Parent child entries are getting inserted as well, but while I am trying to update the the Parent (Where new changes are present both in parent & child), new child entries are getting inserted in child table with the updated data instead of updating the old child data.
Question 2
I am making a patch call here , the data is coming from UI as json, I have some audit trail fields like createdBy, createdTimestamp, updatedBy, updatedTimestamp. These fields are getting populated in backend service in Create & Update operations respectively. Now in update operation, my dto don't have any values for createdBy & createdTimestamp, so in DB it is getting set as null.I am confused here, I am using a patch call then it should retain the old value right ?
Please suggest If I have missed any
Parent:
#Id
#SequenceGenerator(name = "DIVERSITY_TEMPLATE_ID", sequenceName = "DIVERSITY_TEMPLATE_ID", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DIVERSITY_TEMPLATE_ID")
#Column(name = "DIVERSITY_TEMPLATE_ID")
private Integer diversityTemplateId;
#Column(name = "LABEL")
private String label;
#Column(name = "RELATIONSHIP_TYPE")
private String relationshipType;
#Column(name = "CREATED_BY")
private String createdBy;
#Column(name = "CREATED_TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
private Date createdTimestamp;
#Column(name = "UPDATED_BY")
private String updatedBy;
#Column(name = "UPDATED_TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
private Date updatedTimestamp;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "diversityTemplate", fetch = FetchType.LAZY)
private List<DiversityTemplateAttribute> attributes = new ArrayList<>();
/**
* #return the attributes
*/
public List<DiversityTemplateAttribute> getAttributes() {
return attributes;
}
/**
* #param attributes the attributes to set
*/
public void setAttributes(List<DiversityTemplateAttribute> attributes) {
for (DiversityTemplateAttribute diversityTemplateAttribute : attributes) {
diversityTemplateAttribute.setDiversityTemplate(this);
}
this.attributes = attributes;
}
Child:
#Id
#SequenceGenerator(name = "DIVERSITY_TEMPLATE_ATTR_ID", sequenceName = "DIVERSITY_TEMPLATE_ATTR_ID", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DIVERSITY_TEMPLATE_ATTR_ID")
#Column(name = "DIVERSITY_TEMPLATE_ATTR_ID")
private Integer diversityTemplateAttributeId;
#Column(name = "AA")
private String aa;
#Column(name = "BB")
private String bb;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DIVERSITY_TEMPLATE_ID", referencedColumnName = "DIVERSITY_TEMPLATE_ID")
private DiversityTemplate diversityTemplate;
Sample Update JSON
{
"diversityTemplateId": 681,
"label": "SAMPLE_LABEL_463_UPDATED",
"relationshipType": "Married",
"attributes": [{
"diversityTemplateId": 681,
"diversityTemplateAttributeId": 3006,
"aa": "AA",
"bb": "BB Updated",
}, {
"diversityTemplateId": 681,
"diversityTemplateAttributeId": 3006,
"aa": "aa Updated",
"bb": "bb"
}
]
}
Service Layer:
DiversityTemplate updatedEntity = diversityTemplateRepository.save(diversityTemplate);
Question 3
In case of Mapping back the Entity object (when I get it from GET/CREATE operation) to DTO object I am not able to set the FK id in child object , So as a workaround I am iterating through the child list of objects & setting the pk of parent in the child DTO manually, is there any better way of doing this. I have added anothe transient column in the child ENTITY class with same pk Column name as in Parent, but then also it's value is coming as zero, is there any better way ? Please find below my work around.
DiversityTemplate updatedEntity = diversityTemplateRepository.save(diversityTemplate);
List<DiversityTemplateAttributeDTO> attrbiteList = new ArrayList<>();
for (DiversityTemplateAttribute attribute : updatedEntity.getAttributes()) {
DiversityTemplateAttributeDTO attributeDTO = resourceMapper
.convertToDiversityTemplateAttributeDTO(attribute);
attributeDTO.setDiversityTemplateId(updatedEntity.getDiversityTemplateId());
attrbiteList.add(attributeDTO);
}
DiversityTemplateDTO updatedDiversityTemplateDTO = resourceMapper.convertToDiversityTemplateDTO(updatedEntity);
diversityTemplateDTO.setAttributes(attrbiteList);
Please suggest
I had all the same issues as you.
Answer 1: What I ended up doing was setting the orphanRemoval flag equal to true on the #OneToMany. if you do this just be aware that you can no longer set that list of children equal to a new list or else it will through and error. You have to remove and append depending on what you want to delete and add.
Answer 2: You are missing the #EntityListeners(AuditingEntityListener.class) at the top of you parent class but just in case that doesn't work, here is a link to what I used to get it working https://dzone.com/articles/spring-data-jpa-auditing-automatically-the-good-stuff
Answer 3: The reason the id is not setting is because in the java code you have to set the reference to the parent in the child.
i.e. child.setDiversityTemplate(parent);
Once you do that it will properly map it in the table and you'll be able to retrieve it no problem.
I'm struggling with Hibernate entities and JSON in these days and, although there is a lot of questions regarding the object, I'm yet not capable to serialize in presence of circular dependencies. I tried with both Gson and jackson but I didn't get a lot of progresses.
Here is an excerpt from my objects.
This is the "parent" class.
#Entity
public class User extends RecognizedServerEntities implements java.io.Serializable
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = false)
#Cascade({CascadeType.SAVE_UPDATE})
private Set<Thread> threads = new HashSet<Thread>(0);
//...other attributes, getters and setters
}
and this is the "children" class
#Entity
#Table(name = "thread")
public class Thread extends RecognizedServerEntities implements java.io.Serializable
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author", nullable = true)
private User user;
//...other attributes, getters and setters
}
I've written a simple class to test both gson and jackson features; as said, they both raise an exception.
public class MyJsonsTest
{
private static User u;
public static void main(String[] args)
{
u = new User("mail", "password", "nickname", new Date());
u.setId(1); // Added with EDIT 1
// testGson();
testJackson();
}
private static void testJackson()
{
Thread t = new Thread("Test", u, new Date(), new Date());
t.setId(1); // Added with EDIT 1
u.getThreads().add(t);
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try
{
mapper.writeValue(new File("result.json"), u);
}
catch {/[various exceptions catched, but a JsonMappingException was thrown]}
}
private static void testGson()
{
Gson gson = new Gson();
System.out.println(u.toString());
System.out.println(gson.toJson(u, User.class));
Thread t = new Thread("Test", u, new Date(), new Date());
u.getThreads().add(t);
//This raise an exception overflow
System.out.println(gson.toJson(u, User.class));
}
}
To solve the problem, on jackson side, I tried to use this annotation
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
on both User and Thread class. However, it doesn't solve the problem.
On gson side, I read about the GraphAdapterBuilder class, but I wasn't able to properly use it. I don't find any jar, so I copy/pasted the source code from here. However, there is a compile time error at this line
private final ConstructorConstructor constructorConstructor = new ConstructorConstructor();
because the ConstructorConstructor() is undefined; the right syntax should be
ConstructorConstructor(Map<Type>, InstanceCreator<?> instanceCreators)
So, is there a definitive solution to this problem? Obviously, I can't use transient variables.
EDIT 1
I finally found the issue with jackson. In the test class, I forgot to initialize the id field (in real scenarios it is initialized by the database) and this is the reason of the exception. When I finally set the id, all works. This is the output
{
"id" : 1,
"email" : "mail",
"password" : "password",
"nick" : "nickname",
"registeredDate" : 1414703168409,
"threads" : [ {
"id" : 1,
"thread" : null,
"user" : 1,
"title" : "Test",
"lastModifiedDate" : 1414703168410,
"createdDate" : 1414703168410,
"messages" : [ ],
"threads" : [ ]
} ],
"messages" : [ ]
}
When dealing with circular dependencies you need to build a parent-children JSON hierarchy, because the marshalling must be cascaded from root to the inner-most child.
For bi-directional associations, when the Parent has a one-to-many children collection and the child has a many-to-one reference to Parent, you need to annotate the many-to-one side with #JsonIgnore:
#Entity
#Table(name = "thread")
public class Thread extends RecognizedServerEntities implements java.io.Serializable
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#org.codehaus.jackson.annotate.JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author", nullable = true)
private User user;
//...other attributes, getters and setters
}
This way you will no longer have a Json serializing-time circular dependency.
Jackson
As said, I was able to solve the problem using
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=MyEntity.class)`
for each entity as suggested here.
The scope attribute was necessary to make sure that the name "id" is unique within the scope. Actually, without the scope attribute, as you can see here, it throws an exception saying
com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey#3372bb3f] (through reference chain: ParentEntity["children"]->java.util.ArrayList[0]->ChildEntity["id"])
...stacktrace...
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey#3372bb3f]
...stacktrace...
Gson
I still haven't found a clean way to serialize circular dependencies.
I have done this using org.codehaus.jackson.annotate.JsonManagedReference and org.codehaus.jackson.annotate.JsonBackReference in this way...
look at how i used #JsonManagedReference
#Id
#TableGenerator(name="CatId", table="catTable",pkColumnName="catKey",pkColumnValue="catValue", allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE, generator="CatId")
#Column(name = "CategId", unique = true, nullable = false)
private long categoryId;
private String productCategory;
#JsonManagedReference("product-category")
#OneToMany(targetEntity=ProductDatabase.class,mappedBy="category", cascade=CascadeType.ALL, fetch=FetchType.EAGER)
private List<ProductDatabase> catProducts;
and then at the other end i used #JsonBackReference as shown below.
#Id#GeneratedValue
private int productId;
private String description;
private int productPrice;
private String productName;
private String ProductImageURL;
#JsonBackReference("product-category")
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CategId")
private Category category;
just apply these annotations and check if it works for you.
Its not good design to serialize Hibernate POJO to client. As you may send some data to client location, which he is not authorize to view. You should create client POJO and copy data from hibernate POJO to client POJO, which you want to send to client. If you don't want to do that, you can use #JsonIgnore or Fetch all data eagerly.
Scenario :
I have 3 tables, Offer, Channel and Offer_Channels.
Basically Channel is a lookup table, i.e, the values in that table can neither be inserted nor updated by the application. An offer can contain one or many channels. I use the Channel table values to populate dynamic checkboxes. Anyways, so here is what I have :
#Entity
#Table(name = "OFFER")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Offer implements Serializable {
// Offer Id
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "offer_seq_gen")
#Column(name = "OFFER_ID")
private long OfferId;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "OFFER_CHANNELS", joinColumns = { #JoinColumn(name = "OFFER_ID") }, inverseJoinColumns = { #JoinColumn(name = "CHANNEL_ID") })
private Set<Channel> channels = new HashSet<Channel>();
//Other fields and corresponding getters and setters
}
Here is the Channel entity :
#Entity
#Table(name = "CHANNEL")
public class Channel implements Serializable {
private static final long serialVersionUID = 1L;
#NotNull
#Id
#Column(name = "CHANNEL_ID", insertable=false, updatable=false)
private Long channelId;
#Column(name = "CHANNEL_NAME", insertable=false, updatable=false)
private String channelName;
//getters and setters
}
Now, when a user creates an offer, I need to insert row in Offer table and Offer_Channels tables and do nothing(No updates/inserts) for Channel table. Initially, all three would happen, so to achive the "do nothing to Channel table" part, I put insertable=false and updateable=false on the Channel table columns and that worked like a charm. Now I used plain Hibernates for this. I mean I wrote a standalone java application and a main class to add an offer useing hibernate's session.save(offer). It ran the following queries :
Hibernate: insert into OFFER
Hibernate: insert into OFFER_CHANNELS
Alright, now, I have a rest service where I am using the Spring's JPA repository to save the information and I have the same domain objects setup. Now, when I add an offer, it runs :
Hibernate: insert into OFFER
Hibernate: insert into CHANNEL ( It is failing here obviously. I don't want this step to happen)
My question :
1. Why is it is trying to write something to Channel table even though I gave insertable=false in its domain object, and this is only happening with the Spring JPA setup. With the hibernate setup it just works fine.
3. Doesn't #JoinTable/ #OneToMany / insertable / updateble , go well with Spring JPA repository ?
What am I missing here ?
UPDATE :
#Service
#Transactional
public class OfferService {
#Inject
private OfferRepository offerRepository;
public Offer saveOfferInformation(Offer offer) {
log.debug("Saving Offer Info..");
log.debug("Offer object :"+offer);
return offerRepository.save(offer);
}
}
Repo :
public interface OfferRepository extends JpaRepository<Offer, Long> {
List<Offer> findByBuySku(String buySku);
}
And in the REST service Im just injecting the service and calling it, so no business logic in the REST service. Right now Im getting and the reason is it is trying to insert record to Channel table:
exception: "org.springframework.dao.DataIntegrityViolationException"
message: "could not execute statement; SQL [n/a]; constraint [PVS_OWNER.CHANNEL_PK]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement"
Have you tried to add insertable and updatable on the #JoinColumn. This works with One to Many relationships. I'm not 100% sure if it works with a Many to Many relationship.
#JoinTable(name = "OFFER_CHANNELS", joinColumns = { #JoinColumn(name = "OFFER_ID", insertable = false, updatable = false ) }, inverseJoinColumns = { #JoinColumn(name = "CHANNEL_ID", insertable = false, updatable = false ) })