I have a database with a master and child tables where each child table has the same properties but of a different type (boolean, string, date).
This is the master entity
#Setter
#Getter
#Entity
#Table(name = "FIELDS_MASTER")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Master{
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
private String id;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "document")
private Document document;
public abstract String getTypeName();
public abstract Object getValue();
public abstract void setValue(Object value);
}
This are 2 child entitys
#Setter
#Getter
#Entity
#Table(name = "FIELDS_STRING")
#PrimaryKeyJoinColumn(name = "id")
public class StringFieldValue extends FieldValue {
private String value;
#Override
public String getTypeName() {
return FieldEnum.STRING.name();
}
#Override
public String getValue() {
return value;
}
#Override
public void setValue(Object value) {
this.value = value.toString();
}
}
#Setter
#Getter
#Entity
#Table(name = "FIELDS_BOOLEAN")
#PrimaryKeyJoinColumn(name = "id")
public class BooleanFieldValue extends FieldValue {
private LocalDate value;
#Override
public String getTypeName() {
return FieldEnum.BOOLEAN.name();
}
#Override
public Boolean getValue() {
return value;
}
#Override
public void setValue(Object value) {
this.value = Boolean.valueOf(waarde.toString());
}
}
Here is my question, how can I make a jpql query that can access the field "value" of the child components. I would like to search in any child component where the value is equal to.
Example:
The table FIELDS_BOOLEAN has a record where the value is false and the table FIELDS_STRING has a record with value "false", I want both of them.
This is what I would expect to work but does not:
#Query("SELECT m from FIELDS_MASTER WHERE m.getValue() = :searchTerm")
List<FieldsMaster> findByValue(String searchTerm);
I have found that it is not plausibel, the only solution is to acces the child table directly. Like this:
#Query("SELECT m from FIELDS_STRING WHERE m.value = :searchTerm")
List<FieldString> findByValue(String searchTerm);
Related
The bounty expires in 3 days. Answers to this question are eligible for a +50 reputation bounty.
BugsOverflow wants to draw more attention to this question:
Expect an answer that shows how should the model classes I have, should look like based on the many to many relations I described
I have a many to many relation between Trees and Nodes:
Tree can have many nodes.
Node can be in many trees.
I also have composite primary key in the Tree table and also in the Node table.
So my model classes look like so:
Tree:
#Entity
#Table(name = "tree")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Tree {
#EmbeddedId
private TreeIdentifier treeIdentifier;
private LocalDateTime creationDate;
}
TreeIdentifier (this is the primary key of the Tree):
#Embeddable
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class TreeIdentifier implements Serializable {
private String treeId;
private String siteId;
private Integer prodVersion;
#Override
public int hashCode() {
return Objects.hash(prodVersion, siteId, treeId);
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TreeIdentifier other = (TreeIdentifier) obj;
return Objects.equals(prodVersion, other.prodVersion) && Objects.equals(siteId, other.siteId)
&& Objects.equals(treeId, other.treeId);
}
private static final long serialVersionUID = 1L;
}
Node:
#Entity
#Table(name = "node")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Node {
#EmbeddedId
private NodeIdentifier nodeIdentifier;
private Long nodePosition;
}
NodeIdentifier (primary key of Node):
#Embeddable
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class NodeIdentifier implements Serializable {
private String nodeId;
private Integer nodeVersionId;
#Override
public int hashCode() {
return Objects.hash(nodeId, nodeVersionId);
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
NodeIdentifier other = (NodeIdentifier) obj;
return Objects.equals(nodeId, other.nodeId) && Objects.equals(nodeVersionId, other.nodeVersionId);
}
private static final long serialVersionUID = 1L;
}
Now I am struggling to figure out how the intermediate table that surges because of this ManyToMany relation should look like in the code:
TreeNode:
#Entity
#Table(name = "tree_node")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class TreeNode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer treeNodeId;
private Tree tree;
private Node node;
}
I was following Baeldung guides and so far I managed to make it work with saving Tree and Node in the database but now for the intermediate table can not figure out how should I annotate the fields properly (also how should I annotate the Tree and Node class too?), can you help me?
Guides I used:
https://www.baeldung.com/jpa-many-to-many
https://www.baeldung.com/jpa-composite-primary-keys
You can make use of #MapsId;
Perhaps this is what you are tying to achieve.
public class TreeNode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer treeNodeId;
#ManyToOne
#MapsId("treeIdentifier")
private Tree tree;
#ManyToOne
#MapsId("nodeIdentifier")
private Node node;
}
I would suggest to use a bridge entity and create one-to-many and many-to-one relationship between Tree, TreeNode and Node entities as shown below:
Tree:
#Entity
public class Tree {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other fields, getter/setter, constructors, equals and hashCode methods ...
#OneToMany(mappedBy = "tree", cascade = CascadeType.ALL, orphanRemoval = true)
private List<TreeNode> treeNodes = new ArrayList<>();
public void addTreeNode(TreeNode treeNode) {
treeNodes.add(treeNode);
treeNode.setTree(this);
}
public void removeTreeNode(TreeNode treeNode) {
treeNodes.remove(treeNode);
treeNode.setTree(null);
}
}
TreeNode:
#Entity
public class TreeNode {
#EmbeddedId
private TreeNodeId treeNodeId = new TreeNodeId();
// other fields, getter/setter, constructors, equals and hashCode methods ...
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("treeId")
#JoinColumn(name = "tree_id", referencedColumnName = "id")
private Tree tree;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("nodeId")
#JoinColumn(name = "node_id", referencedColumnName = "id")
private Node node;
}
TreeNodeId:
#Embeddable
public class TreeNodeId implements Serializable {
#Column(name = "tree_id", nullable = false)
private Long treeId;
#Column(name = "node_id", nullable = false)
private Long nodeId;
// getter/setter, equals and hashCode methods ...
}
Node:
#Entity
public class Node {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other fields, getter/setter, constructors, equals and hashCode methods ...
#OneToMany(mappedBy = "node", cascade = CascadeType.ALL)
private Set<TreeNode> treeNodes = new HashSet<>();
public void addTreeNode(TreeNode treeNode) {
treeNodes.add(treeNode);
treeNode.setNode(this);
}
public void removeTreeNode(TreeNode treeNode) {
treeNodes.remove(treeNode);
treeNode.setNode(null);
}
}
so I have an hibernate Entity called Appointment, in this entity I have a AppointNumber property which itself contains a number property which is a string.
When I persist my Appointment, I need the AppointmentNumber. I got it to work with #Embedded and #Embeddable the other day but this creates a join table Which I can't have.
I tried many other solutions to try and get it to work without join tables but I can't figure it out. (I get lots of ava.lang.IllegalStateException)
Can anyone help?
Thanks!
#Entity(name = "appointments")
public class Appointment {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#OneToOne(mappedBy = "number")
#Fetch(value = FetchMode.SELECT)
private AppointmentNumber appointmentNumber;
Appointment entity
AppointmentNumber, used in Appointment but should not be an entity
public class AppointmentNumber {
#OneToOne
#JoinColumn(name = "appointmentNumber", unique = true, nullable = false)
private String number;
You could do like this:
#Entity(name = "appointments")
public class Appointment {
///....
#Convert(converter = AppointmentNumberConverter.class)
private AppointmentNumber appointmentNumber;
///....
}
#Converter
public class AppointmentNumberConverter implements
AttributeConverter<PersonName, String> {
#Override
public String convertToDatabaseColumn(AppointmentNumber appointmentNumber) {
if (appointmentNumber == null) {
return null;
}
return appointmentNumber.getNumber();
}
#Override
public AppointmentNumber convertToEntityAttribute(String appointmentNumber) {
if (appointmentNumber == null) {
return null;
}
AppointmentNumber result = new AppointmentNumber();
result.setNumber(appointmentNumber);
return result;
}
}
Have a look at JPA Converter documentation.
I am mapping Entities in Hibernate with JPA and Spring Data and when I run application I get
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [id] on this ManagedType [p.s.t..entity.BaseEntity]
at org.hibernate.metamodel.internal.AbstractManagedType.checkNotNull(AbstractManagedType.java:128) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
at org.hibernate.metamodel.internal.AbstractManagedType.getAttribute(AbstractManagedType.java:113) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
at org.hibernate.metamodel.internal.AbstractManagedType.getAttribute(AbstractManagedType.java:111) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:633) ~[spring-data-jpa-2.1.11.RELEASE.jar:2.1.11.RELEASE]
at org.springframework.data.jpa.repository.query.JpaQueryCreator.complete(JpaQueryCreator.java:175) ~[spring-data-jpa-2.1.11.RELEASE.jar:2.1.11.RELEASE]
I have a superclass BaseEntity:
#MappedSuperclass
#Getter
#Setter
public abstract class BaseEntity implements Serializable {
#Id
#GeneratedValue
private Long Id;
private String uuid = UUID.randomUUID().toString();
#Override
public boolean equals(Object that) {
return this == that ||
that instanceof BaseEntity && Objects.equals(uuid, ((BaseEntity) that).uuid);
}
#Override
public int hashCode() {
return Objects.hash(uuid);
}
}
Regular class Task, which extends the BaseClass
#Getter
#Setter
#Table(name = "task")
#Entity
#NoArgsConstructor
#NamedEntityGraph(
name = "Task.detail",
attributeNodes = {
#NamedAttributeNode("attachments"),
#NamedAttributeNode("tags")
}
)
public class Task extends BaseEntity {
private String title;
private String description;
private LocalDateTime createdAt;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "task_id")
private Set<Attachment> attachments = new HashSet<>();
#ManyToMany
#JoinTable(
name = "tags_tasks",
joinColumns = #JoinColumn(name = "task_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id")
)
private Set<Tag> tags = new HashSet<>();
public Task(String title, String description, LocalDateTime createdAt) {
this.title = title;
this.description = description;
this.createdAt = createdAt;
}
public void addAttachment(String filename, String comment) {
attachments.add(new Attachment(filename, comment));
}
public Set<Attachment> getAttachments() {
return attachments;
}
public void addTag(Tag tag) {
tags.add(tag);
}
public void removeTag(Tag tag) {
tags.remove(tag);
}
}
TaskView for JPA query projection:
public interface TaskView {
Long getId();
String getUuid();
String getTitle();
String getDescription();
LocalDateTime getCreatedAt();
}
And JpaRepository interface:
interface TasksCrudRepository extends JpaRepository<Task, Long> {
#EntityGraph(value = "Task.detail", type = EntityGraphType.LOAD)
List<Task> findAll();
List<TaskView> findAllProjectedBy();
}
The last method - findAllProjectedBy() - in the TaskCrudRepository causes the exception pasted at the begnining of this post.
When I remove getId() method from TaskView it starts, but then I am not able to display the id of the Task in the projection.
So the question is what I am missing in this whole classes structure?
I am using:
Spring Boot 2.1.9.RELEASE
Java 11
Hibernate Core 5.3.12.FINAL
JPA 2.2
There is a typo in BaseEntity when defining ID field. Should be camelcase id instead of Id.
#MappedSuperclass
#Getter
#Setter
public abstract class BaseEntity implements Serializable {
#Id
#GeneratedValue
private Long id;
private String uuid = UUID.randomUUID().toString();
#Override
public boolean equals(Object that) {
return this == that ||
that instanceof BaseEntity && Objects.equals(uuid, ((BaseEntity) that).uuid);
}
#Override
public int hashCode() {
return Objects.hash(uuid);
}
}
We have structure something like bellow:
#MappedSuperclass
public abstract class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
#Override
public boolean equals(Object o) {
if (this == o) return true;
BaseEntity that = (BaseEntity) o;
if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) {
return false;
}
return true;
}
#Override
public int hashCode() {
return Objects.hash(getId());
}
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "BASE_ORGANIZATION")
#DiscriminatorColumn(name = "disc")
#DiscriminatorValue("baseOrganization")
public class BaseOrganization extends BaseEntity {
#Column(name = "TITLE")
private String title;
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "BASE_PERSONNEL")
#DiscriminatorColumn(name = "disc")
#DiscriminatorValue("basePersonnel")
public class BasePersonnel extends BaseEntity {
#Column(name = "FIRSTNAME")
private String firstName;
#Column(name = "LASTNAME")
private String lastName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "BASE_ORGANIZATION_ID")
private BaseOrganization baseOrganization;
}
BaseEntity, BasePersonnel and BaseOrganization is in core_project that all projects can used these objects. Now we create a project that depended to core_project. We must to extends BasePeronnel and BaseOrganization based on our business context. For this reason we added some classes in our project like bellow:
#Entity
#DiscriminatorValue("organization")
public class Organization extends BaseOrganization {
#Column(name = "MISSION_Type")
private String missionType;
}
#Entity
#DiscriminatorValue("personnel")
public class Personnel extends BasePersonnel {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "MISSION_ORGANIZATION_ID")
private Organization missionOrganization;
}
Our problem is raised, when we wanna to get all personnel. When we called getAllPersonnel method in PersonnelRepository, hibernate logged bellow message:
WARN o.h.e.i.StatefulPersistenceContext - HHH000179: Narrowing proxy to class org.xyz.organization.Organization - this operation breaks ==
After that, when we see List<Personnel> object, missionOrganization property is null, but baseOrganization property in super class is loaded!
We think when tow class that have SINGLE_TABLE inheritance strategy, hibernate LazyInitializer can not detect correct concrete class.
Also we debugged narrowProxy method in StatefulPersistenceContext class and we understood that concreteProxyClass.isInstance(proxy) returned false. because proxy object have BaseOrganization object in LazyInitializer and concreteProxyClass refer to Organization class!
I have a table like this:
Id int
Source VARCHAR
Target VARCHAR
And I want to query like this:
select * from `TestTable` t1
left join `TestTable` t2 on t1.`Target` = t2.`Target`
and t1.`Id` <> t2.`Id`
So how to code the mapping with java annotation?
And how to query with hibernate?
Mapping One to Many in hibernate with the same table is not a problem you can do with annotations like this:
#Entity
#Table(name = "test1")
public class Test1 implements Serializable {
private int id;
private String source;
private String target;
private List<Test1> others;
#Id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Column(name = "source")
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
#Column(name = "target")
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
#OneToMany(targetEntity = Test1.class)
public List<Test1> getOthers() {
return others;
}
public void setOthers(List<Test1> others) {
this.others = others;
}
}
The problem is the type of your relation because the relations not seems to be with the primary key. Then the relation is not strict "OneToMany". With the query like you write you can change the referencedColumnName in the JoinColumn annotation like this:
#JoinColumn(name = "target", referencedColumnName = "target")
But with this mapping, if the parent object has the same "target" field then the parent object is included as child because you cannot put the other part of the query in the mapping
t1.`Id` <> t2.`Id`