I have class hierarchy like this:
public abstract class CommunicationCampaign {...}
public class EmailCampaign extends CommunicationCampaign {...}
public class SmsCampaign extends CommunicationCampaign {...}
public class Trigger {...}
CommunicationCampaign defines what kind of message should be distribute to our clients and Trigger defines when communication should be distributed. Campaign should point to two triggers, test trigger and production trigger. The code for this look like this:
public abstract class CommunicationCampaign {
...
#OneToOne(targetEntity = Trigger.class, fetch = FetchType.EAGER)
#PrimaryKeyJoinColumn(name = "TEST_TRIGGER_ID")
private Trigger testTrigger;
#OneToOne(targetEntity = Trigger.class, fetch = FetchType.EAGER)
#PrimaryKeyJoinColumn(name = "TRIGGER_ID")
private Trigger trigger;
...
}
My question is how should i map this relation on the Trigger side? This is the first time I'm facing such problem, so I would appreciate any advice. Maybe I shouldn't approach this using #OneToOne mapping, however I would like to avoid solution with #OneToMany mapping, because then I would have to add new column to Trigger indicating if it is test trigger or not. Trigger behavior should be exactly the same regardless it is used as test or regular trigger for specific campaign. Also Trigger is related to other entities in database and I want to do as little modification as possible to it.
public class Trigger {
...
/*How to map relation to SmsCampaign and EmailCampaign?*/
...
}
I'm looking for something like this:
public class Trigger {
...
#OneToOne(mappedBy = "testTrigger or trigger", fetch = FetchType.LAZY)
private EmailCampaign emailCampaign;
#OneToOne(mappedBy = "testTrigger or trigger", fetch = FetchType.LAZY)
private SmsCampaign smsCampaign;
...
}
Related
I'm having a problem with Specification as stated in the title.
Here is my FishingLocation class:
#Entity
#Table(name = "tbl_fishing_location")
public class FishingLocation {
...
#JsonIgnore
#OneToMany(mappedBy = "id", fetch = FetchType.EAGER)
private List<Lake> lakeList;
...
}
And here is my Lake class:
#Entity
#Table(name = "tbl_lake")
public class Lake {
...
#JsonIgnore
#ManyToOne
#JoinColumn
private FishingLocation fishingLocation;
...
}
They both have a StaticMetamodel as follow:
#StaticMetamodel(FishingLocation.class)
public class FishingLocation_ {
public static volatile ListAttribute<FishingLocation, Lake> lakeList;
public static volatile SingularAttribute<FishingLocation, Long> id;
}
#StaticMetamodel(Lake.class)
public class Lake_ {
public static volatile SingularAttribute<Lake, Long> id;
public static volatile SingularAttribute<Lake, FishingLocation> fishingLocation;
}
I have created a Specification to filter FishingLocation as follow (the fishing method is not relevant):
public static Specification<FishingLocation> fishingMethodIdIn(Set<Long> fishingMethodIds) {
if (fishingMethodIds == null || fishingMethodIds.isEmpty()) {
return null;
}
return root.join(FishingLocation_.lakeList)
.join(Lake_.fishingMethodList)
.get(FishingMethod_.id).in(fishingMethodIds);
};
}
The problem is that when I run the program and send a request to filter, Hibernate showed me this SQL query:
select
fishingloc0_.id as id1_7_,
fishingloc0_.active as active2_7_,
...
from
tbl_fishing_location fishingloc0_
.... (some inner joins)
inner join
tbl_lake lakelist4_
on fishingloc0_.id=lakelist4_.id
....
It is supposed to be on fishingloc0_.id=lakelist4_.fishing_location_id. So where is the problem in my code? Huge thanks to anyone helping me with this problem.
Edit: This is just a small fraction of my code. My filter has multiple criteria and join many tables so I cannot just use premade function in FishingLocationRepository. I want to create dynamic query which should be build depend on what user chooses to filter by.
you don't need to call this all using custom queries the Hibernate will take care of everything with OneTO Many and ManyTOOne mappings.
You can get the required data by simply calling the getter methods of its class.
for example, in the case of Fishing Location, you can get all the lakes related to that location by calling new FishingLocation().getLakeList().
same for the other case. https://www.javatpoint.com/hibernate-many-to-one-example-using-annotation
I am making a Spring Boot backend, and I have the following problem. When I get a Software from VersionableFileRepository and call the getSystem function on that I get the actual System within the relationship. But when I get a Documentation from VersionableFileRepository its getSystem function returns null. I handle the Software and Documentation in the same way, and all instance of these have a System.
Illustrated with code:
versionableFileRepository.findById(fileId).get().getSystem() returns a valid System when fileId identify a Software and returns null when a Documentation
What's wrong? Did I mess something up in the implementation?
I have the following classes:
#Entity
public class System {
#Id
#GeneratedValue
private long id;
private String name;
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
#JoinColumn(name = "software_id", referencedColumnName = "id")
private Software software;
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
#JoinColumn(name = "documentation_id", referencedColumnName = "id")
private Documentation documentation;
//other fields, getters and setters...
}
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class VersionableFile {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "file", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<FileVersion> versions = new ArrayList<>();
public abstract System getSystem();
public abstract void setSystem(System system);
//getters and setters...
}
#Entity
public class Software extends VersionableFile {
#OneToOne(mappedBy = "software")
#JsonIgnore
private System system;
#Override
public System getSystem() {
return system;
}
#Override
public void setSystem(System system) {
this.system = system;
}
}
#Entity
public class Documentation extends VersionableFile {
#OneToOne(mappedBy = "documentation")
#JsonIgnore
private System system;
#Override
public System getSystem() {
return system;
}
#Override
public void setSystem(System system) {
this.system = system;
}
}
#Repository
public interface VersionableFileRepository extends CrudRepository<VersionableFile, Long> {
}
Database:
Everything looks good in the database, this is the system table:
And the corresponding objects can be found in the other two tables (software and documentation). Furthermore the appropriate constraints are also defined.
I think this is a JPA issue, because when I get a System object from SystemRepository (not mentioned here) it has the right software and documentation fields.
Thank you in advance for your help!
Have already commented but looking better I think I found something major here.
Proposal 1
Your Entities structure seems good to me. However you have a major Issue with your java code to retrieve those entities back from database.
versionableFileRepository.findById(fileId).get().getSystem()
fileId as well as documentId are plain Long numbers. How would JPA know if you want to retrieve a Software or a Documentation? This will not work. As you have constructed it, it will have separate tables Documentation and Software and each one of those will have a column Id as primary key.
Make it easier for JPA by using specific repositories
#Repository
public interface SoftwareRepository extends CrudRepository<Software, Long> {
}
Then to retrieve software just use softwareRepository.findById(id).get().getSystem()
And
#Repository
public interface DocumentationRepository extends CrudRepository<Documentation, Long> {
}
Then to retrieve documentation just use documentationRepository.findById(id).get().getSystem()
Proposal 2
If you wish to go along the way you are going then I would consider that the error is specifically on your ids that are generated. You want different tables in your case Documentation and Software to have distinct Ids. Then JPA could distinct from the Id what entity you have.
To achieve that you have to change the strategy of generating Ids
public abstract class VersionableFile {
#Id
#GeneratedValue( strategy = GenerationType.TABLE)
private long id;
....
I have a webapp with database where two Entities have many-to-many relationship but I implemented join table manually. When one of the entities gets deleted it deletes all entries in the join table and updates the other entity so all works perfectly fine, but now I'm supposed to write a test for this feature. For tests I am using in-memory database and that's really the only difference, the same methods with the same annotations (and cascade types) are called but I keep getting:
org.hibernate.exception.ConstraintViolationException: integrity constraint violation: foreign key no action; FKC17477FD8940DF2B table ENTITY1_ENTITY2
I didn't paste any code as I don't believe there is anything wrong with it since it's working. I don't ask to fix this for me, I just need to know what is likely to cause this kind of behavior because I've just ran out of ideas and I don't know what else to search for... Thanks
EDIT: here's some code:
#Entity
#Table(name = "interviewer")
public class Interviewer implements Identifiable {
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "interviewer_id")
private Collection<InterviewerTechnology> technologies;
}
#Entity
#Table(name = "technology")
public class Technology implements Identifiable {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "technology_id")
private Collection<InterviewerTechnology> technologies;
}
#Entity
#Table(name = "interviewer_technology")
public class InterviewerTechnology implements Identifiable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
private Interviewer interviewer;
#ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
private Technology technology;
}
#Component
public class TechnologyDao extends AbstractEntityDao<Technology> {
public void remove(Integer id) {
Technology technology = find(id);
em.remove(technology);
}
}
This code does exactly what I want it to do, it just seems like database used for tests does not see CascadeType parameters that do all the job here
I have found the problem and it was #Transactional annotation. All my test DAOs were extending generic test DAO which was annotated with #Transactional and I blindly annotated every single DAO with it again. The problem here is that some operations need to be performed as single transactions (may need flush() after being executed) so that data is available for other operations straight away. Consider following example:
#Transactional
public abstract class AbstractEntityDao<E> {
#PersistenceContext(unitName = "some-persistence")
protected EntityManager em;
public E create(E e) {
em.persist(e);
return e;
}
(...)
}
which means that every method in this class is a transaction. Now if we annotate another class that extends this class with #Transactional every method will be another transaction, which means if we delete several things in one method it should take several transactions (they all need flush() method to be called in order to execute cascade) but instead they will run as one transaction (unless we specify Propagation). Let this be a lesson for everyone (especially me) to think carefully about which operations need separate transactions and which can be executed as one.
I'm trying to write a hibernate adapter for an old database schema. This schema does not have a dedicated id column, but uses about three other columns to join data.
On some tables, I need to use coalesce. This is what I came up with so far:
About the definition:
A car can have elements, assigned by the car's user or by the car's group of users.
If FORIGN_ELEMENT holds a user's name, definition will be 'u'
If FORIGN_ELEMENT holds a group's name, definition will be 'g'
This also means, one table (CAR_TO_ELEMENT) is misused to map cars to elements and cargroups to elements. I defined a superclass CarElement and subclasses CarUserElement and CarGroupElement.
state is either "active" or an uninteresting string
I set definitition and state elsewhere, we do not need to worry about this.
Use DEP_NR on the join table. If it's zero, use USR_DEP_NR. I did this with COALESCE(NULLIF()) successfully in native SQL and want to achieve the same in Hibernate with Pojos.
Okay, here we go with the code:
#Entity
#Table(name="CAR")
public class Car extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="USER_NAME")
#Type(type="TrimmedString")
private String username;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=CarGroup.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private CarGroup group;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarUserElement.class, mappedBy="car")
private Set<CarUserElement> elements;
}
#Entity
#Table(name="CAR_GROUP")
public class CarGroup extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="GROUP_NAME")
#Type(type="TrimmedString")
private String group;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Car.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private Set<Car> cars;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarGroupElement.class, mappedBy="car")
private Set<CarGroupElement> elements;
}
#MappedSuperclass
public class CarElement extends TableEntry {
#Id
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Element.class)
#JoinColumns(value={
#JoinColumn(name="ELEMENT_NAME"),
#JoinColumn(name="state"),
})
private Element element;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarGroupElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="GROUP_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
I tried all available versions of hibernate (from 3.5.1 [first version with #JoinColumnsOrFormulas] up to 4.x.x), but I always get this error:
Exception in thread "main" java.lang.ClassCastException: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:351)
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1338)
at org.hibernate.cfg.annotations.CollectionBinder.bindOneToManySecondPass(CollectionBinder.java:791)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:719)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:668)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:66)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1597)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1355)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1737)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1788)
Other hibernate users seem to have the same problem: They can't get it working with any version, see this thread and other stackoverflow questions:
https://forum.hibernate.org/viewtopic.php?f=1&t=1010559
To be more complete, here's my TrimmedString Class:
https://forum.hibernate.org/viewtopic.php?p=2191674&sid=049b85950db50a8bd145f9dac49a5f6e#p2191674
Thanks in advance!
PS: It works with joining just these three colulmns with just one DEP-NR-Column (i.e. either DEP_NR OR USR_DEP_NR using just #JoinColumns). But I need this coalesce(nullif()).
I ran into a similar problem, and it seems that the issue is that you are using a #Formula inside an #Id. Hibernate wants Ids to be insertable, and Formulas are read-only.
In my case I was able to work around the problem by making the individual columns Id properties on their own, and making the joined object a separate property. I don't know if this would work in your case since you're using two different columns in your formula, but if so your code might look something like:
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#Column(name="DEP_NR")
private Integer depNr;
#Id
#Column(name="USR_DEP_NR")
private Integer usrDepNr;
#Id
#Column(name="FORIGN_ELEMENT")
private String userName;
#Id
#Column(name="STATE")
private String state;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME", insertable = false, updatable = false)),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE", insertable = false, updatable = false))
})
private Car car;
}
Join formulas are very fragile in Hibernate for the time being; I always had a difficult time to get them work properly.
The workaround that helped me often was to create database views which exposed the proper columns (including foreign keys that don't exist in the original tables). Then I mapped the entities to the views using classing Hibernate/JPA mappings.
Sometimes there are redundant joins in the generated SQL when using such entities, but the database optimizes such queries in most cases so that the execution plan is optimal anyway.
Another approach could be using #Subselects, which are some kind of Hibernate views, but I expect them to be less performant than the classic database views.
I ran into the cast exception as well and I'm on Hibernate 5.x.
Until Hibernate dedicates time to fix the issue, I found that while this guy's approach may not be cleanest (he even eludes to that fact!), it works.
You just need to add the #Column mappings (and get/set methods) to your association table objects that are returning null and manually set the values when you populate the relation data. Simple but effective!
I have the following set of classes
#Embeddable
public class SharedClass {
...
private String legacyField;
...
}
#Entity
public class LongStandingEntity {
...
private SharedClass sharedClass;
...
}
Where legacyfield stores some obsolete data in older objects but is not collected for new ones. However I do need to able to continue to access the old data.
I'm creating a new entity that also makes use of SharedClass
#Entity
public class NewEntity {
...
private SharedClass sharedClass;
...
}
It doesn't need legacyField and I'd like to avoid having to add a column in the database for it whilst keeping the mapping working for LongStandingEntity.
Is this possible with JPA annotations?
I don't recall a particular annotation that can help you with that. Still I'd rather take a design approach with this situation. If you have field that belongs only to a certain class separate them in another embedabble class that's associated only with the older data.
Seems like a cleaner approach to me, just to keep entities clearly differenciated.
One workaround I have found is to override the association with the parameters insertable=false and updatable=false.
#Entity
public class NewEntity {
...
#Embedded
#AssociationOverride(name = "legacyField", joinColumns = #JoinColumn(insertable = false, updatable = false))
private SharedClass sharedClass;
...
}
I fixed the problem as following
#Entity
public class NewEntity {
...
#AttributeOverrides({
#AttributeOverride(name = "legacyField", column = #Column(insertable = false, updatable = false))
})
#Embedded
private SharedClass sharedClass;
...
}
UPDATE
but when reading entity I get the
ERROR: column new_entity_.legacyField does not exist at character 1504
so the problem is NOT fixed