I have two related entities with two different types(GENERAL and CUSTOM) and I save it in the same table. Entity with type GENERAL should have unique values of field name and CUSTOM can have duplicates for different users and not duplicate GENERAL name.
I'm looking for a way to create conditional unique constraint in order to check next cases:
if entity has type GENERAL, name should be unique
if entity has type CUSTOM, name can be duplicated in the table but can't duplicate GENERAL items and should be unique for specific user(by user id)
#Entity
#Table(name = "Purpose", uniqueConstraints = #UniqueConstraint(columnNames = {"purposeId"}))
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class GeneralPurpose {
#Id
#GeneratedValue(strategy=GenerationType.AUTO, generator="purpose_seq_gen")
#SequenceGenerator(name="purpose_seq_gen", sequenceName="PURPOSE_SEQ")
#Column(name = "purposeId", nullable = false)
private long purposeId;
#Column(name = "type", nullable = false)
#Enumerated(EnumType.STRING)
private PurposeType type;
#Column(name = "name", nullable = false)
private String name;
#Entity
#Table(name = "Purpose")
public class CustomPurpose extends GeneralPurpose {
#ManyToOne()
#JoinColumn(name="id")
#JsonIgnore
private User user;
public enum PurposeType {
GENERAL, CUSTOM
}
You could do this by adding another column to the Purpose table. This column is to store a constant value for GENERAL records, and the user id for CUSTOM records. For GENERAL records, the value could be 0 (if the user id is numeric) or "GENERAL" (if the user id is a string). It could be named 'userOfRecord' or 'recordDiscriminator', something like that.
Then you can add a unique constraint on [ type, name, userOfRecord ].
Related
I have generated master tables using liquibase. I have created the corresponding models in spring boot now I want to maintain a relation ship between those models.
I have one table called Vehicle_Type, it is already pre-populated using liquibase.
#Data
#Entity
#Table(name="VEHCILE_TYPE")
public class VehicleType {
#Id
private int id;
#Column(name="DISPLAY_NAME")
private String displayName;
#Column(name="TYPE")
private String type;
#Column(name="CREATED_DATE")
private LocalDateTime createdDate;
#Column(name="UPDATED_DATE")
private LocalDateTime updateDate;
}
now what I want to achieve is, I have one child entity, I have refer the VehicleType instance inside that entity as depicted below
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
#Table(name = "NON_MSIL_VEHICLE_LAYOUT")
public class NonMsilVehicleLayout extends BaseImagesAndLayout {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "NMV_SEQ")
#SequenceGenerator(sequenceName = "NON_MSIL_VEH_SEQUENCE", allocationSize = 1, name = "NMV_SEQ")
private int id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
public interface VehType {
String getVehType();
}
}
The problem is when I tries to save entity NonMsilVehicleLayout, then it tries to first insert the data in VEHICLE_TYPE table also. which should not going to be happen.
I don't want that, I want JPA will pick the correct ID from VEHICLE_TYPE table and place it inside the corresponding table for NonMsilVehicleLayout, because the id of VEHICLE_TYPE table is act as foreign key in Non_Msil_Vehicle_Layout table.
log.info("Inside saveLayout::Start preparing entity to persist");
String resourceUri = null;
NonMsilVehicleLayout vehicleLayout = new NonMsilVehicleLayout();
VehicleType vehicleType=new VehicleType();
vehicleType.setType(modelCode);
vehicleLayout.setVehicleType(modelCode);
vehicleLayout.setFileName(FilenameUtils.removeExtension(FilenameUtils.getName(object.key())));
vehicleLayout.setS3BucketKey(object.key());
I know I missed something, but unable to figure it out.
You are creating a new VehicleType instance setting only the type field and set the vehicleType field of NonMsilVehicleLayout to that new instance. Since you specified CascadeType.ALL on NonMsilVehicleLayout#vehicleType, this means to Hibernate, that it has to persist the given VehicleType, because the instance has no primary key set.
I guess what you rather want is this code:
vehicleLayout.setVehicleType(
entitManager.createQuery("from VehicleType vt where vt.type = :type", VehicleType.class)
.setParameter("type", typeCode)
.getSingleResult()
);
This will load the VehicleType object by type and set that object on NonMsilVehicleLayout#vehicleType, which will then cause the foreign key column to be properly set to the primary key value.
Finally, after some workaround, I got the mistake, the column name attribute was incorrect, so I made it correct and remove the referencedColumn and Cascading.
Incorrect:
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
Correct:
#OneToOne
#JoinColumn(name = "VEHICLE_TYPE")
private VehicleType vehicleTypes;
also I have added the annotation #Column in the referende entity VehicleImage
public class VehicleType {
#Id
#Column(name = "ID") // added this one
private int id;
}
That bit workaround solved my problem, now I have achieved what I exactly looking for.
I have an entity TeamActivity:
#Entity
#Table(name = "teams_to_activities")
public class TeamActivity {
#Column(name = "scope_id", nullable = false)
private String scopeId;
#Column(name = "team_id", nullable = false)
private String teamId;
#Column(name = "activity_set_id", nullable = false)
private String activitySetId;
#Id
#Column(name = "scoped_team_activity_id", nullable = false)
private String scopedTeamActivityId;
}
And another entity ActivitySet:
#Entity
#Table(name = "activity_sets")
public class ActivitySet {
#Column(name = "scope_id", nullable = false)
private String scopeId;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "description", nullable = false)
private String description;
#Id
#Column(name = "scoped_activity_set_id", nullable = false)
private String scopedActivitySetId;
}
There's no index on any other column besides the PK in both tables.
There's no FK constraint creating a relationship between these tables whatsoever. I have no idea why as this is a legacy system.
Technically, if I fetch a TeamActivity record, I can pick the scope_id and activity_set_id from it and combine them to form a scoped_activity_set_id which would be a valid PK to fetch the corresponding ActivitySet.
I know that TeamActivity -> ActivitySet is a N -> 1 association
I would like to leverage Spring Data JPA features to create an association from TeamActivity to ActivitySet such that when I fetch a TeamActivity from TeamActivityRepository, the corresponding ActivitySet is also returned.
I have created an association like this before using a combination of #JoinColumn and #MapsId but there was actually a single FK to use which is different here where source table has 2 columns I can combine to get the target's key.
If you are fully in control of the database, I may propose you create a Materialized View with the contents you desire from both tables and handle it as any other table with JPA, i.e, create #Entity model and CrudRepository<MVTeamActivitySet, String>.
If you are not fully in control of the database, one easy way to achieve it is to simply create a method that internally executes two lookup queries and retrieves the expected model you want. You will still be using using JPA correctly.
Querying two tables and joining desired fields in the code layer is quite common with denormalized DBs, sometimes you want to avoid the overhead of a Materialized View.
#Override
public TeamActivitySetDto findById(String scopedTeamActivityId) throws DemoCustomException {
Optional<TeamActivity> teamActivityEntity = teamActivityDao.getById(scopedTeamActivityId);
if(teamActivityEntity.isEmpty()) {
throw new DemoCustomException("teamActivity record not found");
}
String scopedActivitySetId =
teamActivityEntity.get().getScopeId() + ":" + teamActivityEntity.get().getActivitySetId();
Optional<ActivitySet> activitySetEntity = activitySetDao.getById(scopedActivitySetId);
if(activitySetEntity.isEmpty()) {
throw new DemoCustomException("activitySet record not found");
}
return TeamActivitySetDto.builder()
.description(activitySetEntity.get().getDescription())
.name(activitySetEntity.get().getName())
.scopedActivitySetId(activitySetEntity.get().getScopedActivitySetId())
.activitySetId(teamActivityEntity.get().getActivitySetId())
.scopedTeamActivityId(teamActivityEntity.get().getScopedTeamActivityId())
.scopeId(teamActivityEntity.get().getScopeId())
.teamId(teamActivityEntity.get().getTeamId())
.build();
}
So lets imagine following situation. I have an entity such as this:
#Entity
public class Price {
#Id
private int id;
#Column
private int amount;
private String currency;
}
And I have two tables:
CREATE TABLE currency (
id integer not null primary key,
name varchar
);
CREATE TABLE price (
id integer not null primary key,
amount integer,
currency_id integer references currency(id)
);
I want to tell Spring that when I access Price.getCurrency() I want to have whatever is stored in column "name" of the "currency" table. In other words, I want to connect two tables in one entity.
I can make currency a separate class, annotate the property with #OneTo... and get it like price.getCurrency().getName(). But I don't want a separate class, I just need this specific column.
I tried adding it via #SecondaryTable annotation like this:
#SecondaryTable(name = "currency",
pkJoinColumns = #PrimaryKeyJoinColumn(name = "id", referencedColumnName = "currency_id"))
But in this case Spring connect two tables by it's ids like this:
SELECT * FROM price LEFT JOIN price ON price.id = currency.id
And of course it is not working. So how do I do this? Is #SecondaryTable a correct way and if so how do I connect it through non-primary key column?
Yes, you can use #SecondaryTable:
#Entity
#Table(name = "price")
#SecondaryTable(
name = "currency",
pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "currency_id")
})
public class Price {
#Id
private int id;
#Column
private int amount;
#Column(table = "currency", name = "name")
private String currency;
}
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.
Q 1) How can we model a ternary relationship using Hibernate? For example, how can we model the ternary relationship presented here using Hibernate (or JPA)?
NOTE: I know that JPA 2 has added some constructs for building ternary relationships using maps. However, this question assumes JPA 1 or Hibernate 3.3.x and I don't like to use maps to model this.
(source: grussell.org)
(source: grussell.org)
Ideally I prefer my model to be like this:
class SaleAssistant {
Long id;
//...
}
class Customer {
Long id;
//...
}
class Product {
Long id;
//...
}
class Sale {
SalesAssistant soldBy;
Customer buyer;
Product product;
//...
}
Q 1.1)
How can we model this variation, in which each Sale item might have many Products?
class SaleAssistant {
Long id;
//...
}
class Customer {
Long id;
//...
}
class Product {
Long id;
//...
}
class Sale {
SalesAssistant soldBy;
Customer buyer;
Set<Product> products;
//...
}
Q 2) In general, how can we model n-ary, n >= 3 relationships with Hibernate?
Thanks in advance.
Q1. How can we model a ternary relationship using Hibernate? For example, how can we model the ternary relationship presented here using Hibernate (or JPA)? (...)
I would remodel the association with an intermediate entity class (and that's the recommended way with Hibernate). Applied to your example:
#Entity
public class Sale {
#Embeddable
public static class Pk implements Serializable {
#Column(nullable = false, updatable = false)
private Long soldById;
#Column(nullable = false, updatable = false)
private Long buyerId;
#Column(nullable = false, updatable = false)
private Long productId;
public Pk() {}
public Pk(Long soldById, Long buyerId, Long productId) { ... }
// getters, setters, equals, hashCode
}
#EmbeddedId
private Pk pk;
#ManyToOne
#JoinColumn(name = "SOLDBYID", insertable = false, updatable = false)
private SaleAssistant soldBy;
#ManyToOne
#JoinColumn(name = "BUYERID", insertable = false, updatable = false)
private Customer buyer;
#ManyToOne
#JoinColumn(name = "PRODUCTID", insertable = false, updatable = false)
private Product product;
// getters, setters, equals, hashCode
}
Q1.1. How can we model this variation, in which each Sale item might have many Products?
I wouldn't use a composite primary key here and introduce a PK for the Sale entity.
Q2. In general, how can we model n-ary, n >= 3 relationships with Hibernate?
I think that my answer to Q1. covers this. If it doesn't, please clarify.
Update: Answering comments from the OP
(...) the pk's fields are not getting populated and as a result I cannot save Sale items in the DB. Should I use setters like this for the Sale class? public void setBuyer(Customer cust) { this.buyer = cust; this.pk.buyerId = cust.getId(); }
You need to create a new Pk (I removed the constructors from my original answer for conciseness) and to set it on the Sale item. I would do something like this:
Sale sale = new Sale();
Pk pk = new Pk(saleAssistant.getId(), customer.getId(), product.getId());
sale.setPk(pk);
sale.setSoldBy(saleAssistant);
sale.setBuyer(customer);
sale.setProduct(product);
...
And then persist the sale.
Also, in the JoinColumn annotations, what column are "name" fields referring to? The target relations' pks or the sale table's own column names?
To the columns for the attributes of the composite Pk (i.e. the sale table's own column names), we want them to get PK and FK constraints.
Are you using database generated primary keys for Customer, Product and SalesAssistant? That might cause an issue since it looks like you're trying to use the actual DB identities rather than letting Hibernate resolve the object references during actual persistence.
The embedded PK above looks odd to me personally but I've not had a chance to try it out. It seems like the columns are overlapping and clobbering each other.
I would think it sufficient to just have the ManyToOne references.
Also, turn on SQL statement debugging and see what's being sent to the DB.