I want to create a tree structure where each node can have multiple parents and children. (So actually it is not really a tree but more of a network).
For example, we have an interface to implement the composition, a User class which is the leaf node and a Group class which builds the structure. There would be some check against recursion (adding a group to a group that had the first group as a parent somewhere).
interface GroupMember {
boolean isLeaf();
}
class User implements GroupMember {
private int id;
private String name;
boolean isLeaf() { return true; }
}
class Group implements GroupMember {
private int id;
private Set<GroupMember> members;
boolean isLeaf() { return false; }
public addMember(GroupMember newMember) {
// Some check against recursion
members.add(newMember);
}
}
I see the most efficient way of implementing this in the database would be to have a link table (though this is just a suggestion and not required):
TABLE GROUP_MEMBER
-------------------
PARENT_ID NUMBER
CHILD_TYPE CHAR(1)
CHILD_ID NUMBER
However, I am not sure if Hibernate supports this design. It seems to me that in loading the members set in Group Hibernate would have to consider the discriminator in the GROUP_MEMBER table to decide which class to instantiate.
I have considered having group containing two sets to separately fetch the groups and users, but this seems less than ideal.
May be I'm wrong, but I don't agree with having CHILD_TYPE to be part part of GROUP_MEMBER. I's a CHILD implementation detail and should stay with it. By moving it to the CHILD table, you can use standard ManyToMany JPA mapping, which should make the life simpler.
If desired, CHILD_TYPE can be a discriminator inside the CHILD table.
I always recommend to have a FK. Bugs happen, and orphans in the database are always a huge headache.
Entities:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "CHILD_TYPE", length = 1)
#Table(name = "MEMBERS", schema = "mtm")
#Data //lombok
#EqualsAndHashCode(onlyExplicitlyIncluded = true) //lombok
public abstract class GroupMember {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#ManyToMany
#JoinTable(name = "GROUP_MEMBER", schema = "mtm",
joinColumns = #JoinColumn(name = "MEMBER_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "PARENT_ID", referencedColumnName = "ID"))
private Set<Group> parents = new HashSet<>();
public abstract boolean isLeaf();
}
#Entity
#DiscriminatorValue("G")
#Data
#EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
class Group extends GroupMember {
#ManyToMany(mappedBy = "parents")
private Set<GroupMember> members = new HashSet<>();
public boolean isLeaf() {
return false;
}
}
#Entity
#DiscriminatorValue("U")
#SecondaryTable(name = "USERS", schema = "mtm")
#Data
#EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
class User extends GroupMember {
#EqualsAndHashCode.Include
#Column(table = "USERS")
private String name;
public boolean isLeaf() {
return true;
}
}
Schema:
create schema if not exists MTM;
CREATE TABLE MTM.MEMBERS (
id INT GENERATED BY DEFAULT AS IDENTITY,
CHILD_TYPE CHAR(1)
);
CREATE TABLE MTM.GROUP_MEMBER (
member_id INT,
parent_id INT
);
CREATE TABLE MTM.users (
id INT,
name varchar(255)
);
Notes:
Standard Hibernate MTM and inheritance strategies are implemented
Common data is stored in the MEMBERS table and User specific inside USERS table (implemented using #SecondaryTable)
Group data is stored entirely inside MEMBERS for efficiency (eliminates JOIN), but can be extended in the same way as User
If required, an additional interface can be introduced for the isLeaf() property.
I think you could use a #NamedQuery looking like select g from Group g left join fetch g.members on top of your Group class and use this query with the Hibernate session. Then you would use a query like select g from Group g left join fetch g.members where g.id = :id and get the result then.
Related
Premise:
I have been recently assigned to work with a Java + JPA + Hibernate application.
This application has 4 different "modules" and each is a "copy+paste" of each other, with minor changes.
I want to remove all duplications and work with a single database schema (currently, there is one schema for each "module").
I am trying to start in the least "invasive" way, not changing too much of what's already there.
What I did was:
I created a base module and moved some hibernate entities there.
I made these entities abstract and created implementations for each module.
I create a new schema and moved every other module record to it (I had to disable the database constraints for now).
Example:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "SYSTEM", length = 10, discriminatorType = DiscriminatorType.STRING)
#Table(name = "GROUP")
public abstract class Group<U extends UserGroup> {
#Id
private Integer id;
#Column(name = "CODE")
private String code;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "group")
private List<U> users;
#Entity
#Table(name = "USER_GROUP")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "SYSTEM", length = 10, discriminatorType = DiscriminatorType.STRING)
public abstract class UserGroup<G extends Group> {
#Id
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne(optional=false)
#JoinColumn(name = "GROUP_ID", referencedColumnName = "ID", insertable=false, updatable=false)
private G group;
The implementations just define a discriminator column.
Issue:
The following query:
public interface UserGroupRepository<T extends UserGroup> extends CrudRepository<T, Integer> {
#Query(value = "select grp.code from #{#entityName} ug join ug.group grp where ug.name= ?1")
Iterable<String> listGroupByUser(String name);
Is returning 4 items because my user has a record for each module in the database (it should return only 1 item).
Question:
Using "#Query", can I somehow filter by the discriminator value properly?
According to Hibernate Inheritance doc, you can get this by table per subclass instead of table per class hierarchy strategy.
Yes you can try using the where clause. There is a special class property that you can use to restrict a query to a subtype. Below is the hibernate reference documentation.
In your case you can use the subEntity name instead of DomesticCat.
Click here to see the hibernate documentation of the where clause.
Building a Spring Boot REST service backed by MySQL here. I'm adding a super-simple chat feature to an app and this service will handle its backend/enndpoints. I'm new to JPA and have two concerns: (1) that my primordial data model itself may be a little awry; and (2) that I'm not wrapping that model correctly using JPA conventions/best practices.
So first: an overview of the simple problem I'm trying to solve: Users can send Messages to 1+ other Users. This creates a Conversation, which is really just a container of 1+ Messages. If the Conversation is only between 2 Users, it's considered (by the app) to be a Direct Message (DM). Otherwise its considered to be a Group Chat.
My tables (pseudo-schema):
[users]
=======
id PRIMARY KEY AUTO_INC INT NOT NULL,
username VARCHAR(255) NOT NULL
[conversations]
===============
id PRIMARY KEY AUTO_INC INT NOT NULL,
created_on DATETIME NOT NULL
[messages]
==========
id PRIMARY KEY AUTO_INC INT NOT NULL,
conversation_id FOREIGN KEY INT NOT NULL, # on conversations table
sender_id FOREIGN KEY INT NOT NULL, # on users table
text VARCHAR(2000) NOT NULL,
sent_at DATETIME
[users_x_conversations]
=======================
id PRIMARY KEY AUTO_INC INT NOT NULL,
conversation_id FOREIGN KEY INT NOT NULL, # on conversations table
user_id FOREIGN KEY INT NOT NULL, # on users table
So in my design above, you can see I'm really just using the [conversations] table as a placeholder and as a way of grouping messages to a single conversation_id, and then [users_x_conversations] is crosswalk (many-to-many) table where I'm actually storing who is a "member of" which conversation.
Is this the right approach to take or is there a better way to relate the tables here? That's Concern #1.
Assumning I'm modeling the problem at the database correctly, then I have the following JPA/entity classes:
#MappedSuperclass
abstract public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// Ctors, getters & setters down here...
}
#Entity(name = 'messages')
#AttributeOverrides({
#AttributeOverride(name = 'id', column=#Column(name='message_id'))
})
public class Message extends BaseEntity {
#OneToOne(fetch = FetchType.EAGER, cascade = [CascadeType.PERSIST, CascadeType.MERGE])
#JoinColumn(name = 'conversation_id', referencedColumnName = 'conversation_id')
#NotNull
#Valid
private Conversation conversation;
#OneToOne(fetch = FetchType.EAGER, cascade = [CascadeType.PERSIST, CascadeType.MERGE])
#JoinColumn(name = 'user_id', referencedColumnName = 'user_id')
#NotNull
#Valid
private User sender;
#Column(name = 'message_text')
#NotEmpty
private String text;
#Column(name = 'message_sent_at')
#NotNull
private Date sentAt;
// Ctors, getters & setters down here...
}
#Entity(name = 'conversations')
#AttributeOverrides({
#AttributeOverride(name = 'id', column=#Column(name='conversation_id'))
})
public class Conversation extends BaseEntity {
#Column(name = 'conversation_created_on')
#NotNull
private Date createdOn;
// Ctors, getters & setters down here...
}
What I'm stuck on now is: how should I model my [users_x_conversations] table at the JPA layer? Should I create something like this:
#Entity(name = 'users_x_conversations')
#AttributeOverrides({
#AttributeOverride(name = 'id', column=#Column(name='users_x_conversations_id'))
})
public class UserConversations extends BaseEntity {
#ManyToMany(fetch = FetchType.EAGER, cascade = [CascadeType.PERSIST, CascadeType.MERGE])
#JoinTable(
name="users_x_conversations",
joinColumns=[
#JoinColumn(name="user_id")
],
inverseJoinColumns=[
#JoinColumn(name="conversation_id")
]
)
private Map<User,Conversation> userConversations;
// Ctors, getters & setters down here...
}
Basically my service will want to be able to do queries like:
Given a conversationId, who are all the users that are members of that conversation?; and
Given a userId, what are all the conversations that user is a member of (DM and Group Chat alike)?
Is this the right approach to take or is there a better way to relate the tables here?
Your approach seems OK at the DB layer, except that if users_x_conversations serves only as a join table (i.e. if there are no extra properties associated with the (user, conversation) associations represented within), then I would use (conversation_id, user_id) as its PK instead of giving it a surrogate key. If you don't do that, then you should at least put a uniqueness constraint on that pair.
What I'm stuck on now is: how should I model my [users_x_conversations] table at the JPA layer?
I take you to be asking whether you should model that table as an entity. If you insist on giving it a surrogate key as you have done, then that implies "yes". But as I already discussed, I don't think that's needful. Nor much useful, for that matter. I would recommend instead modeling a direct many-to-many relationship between Conversation and User entities, with this table (less its id column) serving as the join table:
#Entity
#Table(name = "converations")
public class Conversation extends BaseEntity {
#Column(name = 'conversation_created_on')
#NotNull
private Date createdOn;
#ManyToMany(mappedBy = "conversations")
#JoinTable(name = "users_x_conversations",
joinColumns = #JoinColumn(name="conversation_id", nullable = false, updateable = false),
inverseJoinColumns = #JoinColumn(name = "user_id", nullable = false, updateable = false)
)
private Set<User> users;
// Ctors, getters & setters down here...
}
#Entity
#Table(name = "users")
public class User extends BaseEntity {
#NotNull
private String username;
#ManyToMany(mappedBy = "users")
// this is the non-owning side of the relationship; the join table mapping
// is declared on the other side
private Set<Conversation> conversations;
// Ctors, getters & setters down here...
}
Note in that case that User and Conversation entities are directly associated in the object model.
On the other hand, if you did choose to model users_x_conversations via an entity of its own, then the code you present for it is all wrong. It would look more like this:
#Entity
#Table(name = "users_x_converations", uniqueConstraints =
#UniqueConstraint(columnNames={"converation_id", "user_id"}))
public class UserConversation extends BaseEntity {
#ManyToOne(optional = false)
#JoinColumn(name = "conversation_id", nullable = false, updatable = false)
Conversation conversation;
#ManyToOne(optional = false)
#JoinColumn(name = "user_id", nullable = false, updatable = false)
User user;
// Ctors, getters & setters down here...
}
Note well that:
This makes the object-level association between Conversations and Users indirect, via UserConversation entities. If the relationships are navigable from the other side, then they would be modelled via #OneToMany relationship fields of type Set<UserConversation> or List<UserConversation>.
It requires more code, and more objects in the system at runtime.
On the other hand, it does have the minor advantage of saving you from making a somewhat arbitrary choice of which side of a direct #ManyToMany relationship is the owning side.
I'm trying to perform a query to find cars by their foo property. The properties are stored in a different table.
I have two classes
#Embeddable
#Table(name = "PROPERTY")
public class Property {
#Column(name = "type", nullable = false)
private String type;
#Column(name = "string_value", nullable = true)
private String stringValue;
...
}
#Entity
#Table(name = "CAR")
public class Car {
#Id
...
private String id;
#ElementCollection(fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
#CollectionTable(name = "PROPERTY", joinColumns = #JoinColumn(name = "car_ID") )
private Set<Property> properties = new HashSet<Property>();
...
}
I'm trying to perform a query
QueryDsl:
.from(car)
.leftJoin(car.properties, foo)
.on(foo.type.eq("foo"))
.where(predicate)
Resulting HQL:
select
car
from
com....Car car
left join
car.properties as foo with foo.type = :a1
where
...
This doesn't work because of: https://hibernate.atlassian.net/browse/HHH-2772
Before that, it was possible to write HQL:
SELECT cat FROM Cat cat LEFT JOIN cat.kittens as kitten WITH kitten.owner=:owner
Now the HQL is raising an exception:
org.hibernate.hql.ast.InvalidWithClauseException: with clause can only reference columns in the driving table
Workaround is to explicitly use primary key (ownerId):
SELECT cat FROM Cat cat LEFT JOIN cat.kittens as kitten WITH kitten.owner.ownerId=:ownerId
The problem is that I don't have the ownerId, or an owner, since it's an element collection.
If I were to turn the element collection into a #oneToMany #manyToOne, the property could not longer be embeddable and would require an id. This is not an option. (I can't define a composite ID (this is a requirement), and I don't want to add a new column )
What do you recommend?
Is it possible to add the Car or Car Id as a field into an embaddable class?
Can I create the criteria in a different way?
I'm interested in any workaround that doesn't require database changes. (Hibernate changes or ok)
Thank you
I have an abstract BaseEntity class, all concrete entity classes extends from it.
Now there is a "tag" entity and each concrete entity class have many-to-many relationship with tag.
Based on my JPA code I am generating SQL Tables thru "persistence.xml" using "javax.persistence.schema-generation.scripts.create-target"
BaseEntity.java
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity implements Serializable {
#Column(name = "uid")
private Long uid;
/**
* The owning side of a ManyToMany relationship from each entity to Tag
*/
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "baseentity_tag", joinColumns =
{
#JoinColumn(name = "baseuid", referencedColumnName = "uid")
}, inverseJoinColumns =
{
#JoinColumn(name = "taguid", referencedColumnName = "uid")
})
protected List<Tag> tags = new ArrayList<>();
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public void setTags(List<Tag> tags) {
this.tags = tags;
}
public List<Tag> getTags() {
return tags;
}
}
Tag Entity class
#Entity
public class Tag extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* The inverse side of a ManyToMany relationship from Tag to each entity
*/
#ManyToMany(mappedBy = "tags", fetch = FetchType.LAZY)
private List<BaseEntity> baseEntities = new ArrayList<>();
public void setBaseEntities(List<BaseEntity> baseEntities) {
this.baseEntities = baseEntities;
}
#XmlTransient
public List<BaseEntity> getBaseEntities() {
return baseEntities;
}
}
Is there any issue with above design; at line
#JoinColumn(name = "baseuid", referencedColumnName = "uid")
because I am expecting join table "baseentity_tag" (column "baseuid") to have reference in each concrete entity class, say concrete1.java, concrete2.java etc. ?
I guess a common join table "baseentity_tag" will not suffice the need against all Concrete entities.
This is because the generated SQL code for create statement is:
CREATE TABLE baseentity_tag (baseuid VARCHAR(255) NOT NULL, taguid BIGINT NOT NULL, PRIMARY KEY (baseuid, taguid));
ALTER TABLE baseentity_tag ADD CONSTRAINT FK_baseentity_tag_baseuid FOREIGN KEY (baseuid) REFERENCES conrete1 (uid);
ALTER TABLE baseentity_tag ADD CONSTRAINT FK_baseentity_tag_taguid FOREIGN KEY (taguid) REFERENCES tag (uid);
Here there is no reference to conrete2, concrete3 etc tables.
If so is there any generic solution to this or I need to create separate #ManyToMany relations in each concrete entity class ?
The way you set up your relationships is independent from form the inheritance strategy. So the definition of your #ManyToMany relationship with associated #JoinTable definition should work with all the strategies available – SINGLE_TABLE, JOINED and TABLE_PER_CLASS – all of which have different set of tables to support the strategy.
In your case you have selected strategy InheritanceType.TABLE_PER_CLASS, so each of your concrete classes will have its own table including fields inherited from the Abstract Super BaseEntity. Each of these tables contains a uid column (as will table Tag). Each of these concrete classes inherits the #ManyToMany relationship and refers to the JoinTable baseentity_tag which simply links entities based on the uid values.
I'm working on a hibernate entity mapping for a database view; when I do a criteria query against it, hibernate is generating bad SQL. Any help figuring out what the problem is with my mapping would be greatly appreciated!
I have two mapped entities which I am trying to grab from a database view; the view has no other columns, just the FK of each entity. One of these FK's can be treated as a primary key, since the view has a row for each primary entity. So my DB schema for the view looks like:
primary(primary_id, some_other_fields)
history(history_id, primary_id, some_other_fields)
view_latest_status_history(primary_id, history_id)
Note the view is used because I want to pick out only the latest history for each primary, not all mapped history records. Here is the object I am using for the view, with entity annotations:
#Entity
#org.hibernate.annotations.Entity(dynamicUpdate = true)
#Table(name = "view_latest_status_history")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class ViewLatestStatusHistoryRow implements Serializable {
private Primary primary;
private History history;
/**
* #return Returns the history.
*/
#ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.LAZY)
#JoinColumn(name = "history_id", nullable = true)
#AccessType("field")
public History getHistory() {
return history;
}
//equals() and hashCode() implementations are omitted
/**
* #return Returns the primary.
*/
#Id
#ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.LAZY)
#JoinColumn(name = "primary_id", nullable = false)
#AccessType("field")
public Primary getPrimary() {
return primary;
}
}
Both the Primary and History objects have complete, working entity annotations.
My criteria setup:
criteria.add(Restrictions.in("primary", [collection of primary objects]));
criteria.setFetchMode("primary", FetchMode.JOIN);
criteria.setFetchMode("history", FetchMode.JOIN);
And the (wrong) generated SQL:
select this_.primary as primary78_1_, this_.primary_id as prim2_78_1_, primary2_.history_id as unique1_56_0_, ...history fields
from DB_CATALOG.dbo.view_latest_status_history this_
left outer join DB_CATALOG.dbo.history primary2_ on this_.primary_id=primary2_.primary_id
where this_.specChange in (?, ?...)
I might've mucked up a few things when editing out the specifics of our project's DB schema, but the point is the first field in the 'select' clause is wrong:
this_.primary (view_latest_status_history.primary) is not a field; the field should be called primary_id. I think this may have something to do with the #Id annotation on the primary field? Any ideas how to fix this? If I remove the #Id, I get an error telling me that the entity has no primary key.
Update:
I no longer map the view as a field using a join table notation (as suggested below). The annotations have been revised, as follows. This solution works correctly in HQL, and generates the expected schema when hbm2ddl is enabled, but I have not re-tested it using the criteria query.
#Entity
#Table(name = "view_latest_status_history")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ViewLatestStatusHistoryRow implements Serializable {
private String id;
private Primary primary;
private History history;
/**
* #return Returns the history.
*/
#OneToOne(optional = true)
#JoinColumn(name = "history_id", nullable = true)
#AccessType("field")
public History getHistory() {
return history;
}
//equals() and hashCode() implementations are omitted
#Id
#Column(name = "primary_id", nullable = false)
#Override
#AccessType(value = "field")
public String getId() {
return id;
}
/**
* #return Returns the primary.
*/
#PrimaryKeyJoinColumn(name = "primary_id", referencedColumnName = "unique_id")
#OneToOne(optional = false)
#AccessType("field")
public Primary getPrimary() {
return primary;
}
}
It most certainly is due to #Id annotation - primary_id is NOT a primary key in this case. Nor can you realistically have #Id and #ManyToOne on the same property.
Let me ask you this - why are you mapping ViewLatestStatusHistoryRow as an entity to begin with? It's not like you ever going to persist it. Consider mapping your latest history entry directly (as read-only) on primary (as many-to-one) and using your view as join table.