I have a versioning on an entity as part of its primary key. The versioning is done via a timestamp of the last modification:
#Entity
#Table(name = "USERS")
#IdClass(CompositeKey.class)
public class User {
#Column(nullable = false)
private String name;
#Id
#Column(name = "ID", nullable = false)
private UUID id;
#Id
#Column(name = "LAST_MODIFIED", nullable = false)
private LocalDateTime lastModified;
// Constructors, Getters, Setters, ...
}
/**
* This class is needed for using the composite key.
*/
public class CompositeKey {
private UUID id;
private LocalDateTime lastModified;
}
The UUID is translated automatically into a String for the database and back for the model. The same goes for the LocalDateTime. It gets automatically translated to a Timestamp and back.
A key requirement of my application is: The data may never update or be deleted, therefore any update will result in a new entry with a younger lastModified. This requirement is satisfied with the above code and works fine until this point.
Now comes the problematic part: I want another object to reference on a User. Due to versioning, that would include the lastModified field, because it is part of the primary key. This yields a problem, because the reference might obsolete pretty fast.
A way to go might be depending on the id of the User. But if I try this, JPA tells me, that I like to access a field, which is not an Entity:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToOne(optional = false)
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
#Column(nullable = false)
private boolean married;
// Constructors, Getter, Setter, ...
}
What would be the proper way of solving my dilemma?
Edit
I got a suggestion by JimmyB which I tried and failed too. I added the failing code here:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToMany
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private List<User> users;
#Column(nullable = false)
private boolean married;
public User getUser() {
return users.stream().reduce((a, b) -> {
if (a.getLastModified().isAfter(b.getLastModified())) {
return a;
}
return b;
}).orElseThrow(() -> new IllegalStateException("User detail is detached from a User."));
}
// Constructors, Getter, Setter, ...
}
What you seem to require seems to be on the lines of a history table, to keep track of the changes. See https://wiki.eclipse.org/EclipseLink/Examples/JPA/History on how EclipseLink can handle this for you while using normal/traditional JPA mappings and usage.
What you have here is a logical 1:1 relationship which, due to versioning, becomes a technical 1:n relationship.
You have basically three options:
Clean JPA way: Declare an 'inverse' #ManyToOne relationship from user to the "other object" and make sure you always handle it whenever a new User record is created.
'Hack-ish' way: Declare a #OneToMany relationship in the "other object" and force it to use a specific set of columns for the join using #JoinColumn. The problem with this is that JPA always expects unique reference over the join columns so that reading the UserDetail plus referenced User records should work, whereas writing UserDetail should not cascade onto User to avoid unwanted/undocumented effects.
Just store the user's UUID in the "other object" and resolve the reference yourself whenever you need it.
The added code in your question is wrong:
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
More correct, albeit not with the result you want, would be
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private User user;
This won't work though, because, as I said above, you may have more than one user record per UserDetail, so you'd need a #OneToMany relationship here, represented by a Collection<User>.
Another 'clean' solution is to introduce an artificial entity with a 1:1 cardinality w.r.t. to the logical User to which you can refer, like
#Entity
public class UserId {
#Id
private UUID id;
#OneToMany(mappedBy="userId")
private List<User> users;
#OneToOne(mappedBy="userId")
private UserDetail detail;
}
#Entity
public class User {
#Id
private Long _id;
#ManyToOne
private UserId userId;
}
#Entity
public class UserDetail {
#OneToOne
private UserId userId;
}
This way, you can somewhat easily navigate from users to details and back.
I came to a solution, that is not really satisfying, but works. I created a UUID field userId, which is not bound to an Entity and made sure, it is set only in the constructor.
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#Column(nullable = false)
// no setter for this field
private UUID userId;
#Column(nullable = false)
private boolean married;
public UserDetail(User user, boolean isMarried) {
this.id = UUID.randomUUID();
this.userId = user.getId();
this.married = isMarried;
}
// Constructors, Getters, Setters, ...
}
I dislike the fact, that I cannot rely on the database, to synchronize the userId, but as long as I stick to the no setter policy, it should work pretty well.
Related
In a Spring Boot app, I have the following entities that have one-to-many relationship (Category is the parent of Recipe):
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, length = 50)
private String title;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", referencedColumnName = "id")
private Category category;
}
#Entity
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true, nullable = false, length = 50)
private String name;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
private Set<Recipe> recipes = new HashSet<>();
public void addRecipe(Recipe recipe) {
recipes.add(recipe);
recipe.setCategory(this);
}
public void removeRecipe(Recipe recipe) {
recipes.remove(recipe);
recipe.setCategory(null);
}
}
When I create a Recipe, I send categoryId that is selected from Dropdown list and create Recipe by retrieving and adding category to the recipe as shown below:
#Transactional
public void update(RecipeRequest request) {
final Category category = categoryRepository.findById(request.getCategoryId())
.orElseThrow(() -> new NoSuchElementFoundException(NOT_FOUND_CATEGORY));
/* instead of retrieving category, I want to set the categoryId field of Recipe,
but there is not such kind of setter */
recipe.setCategoryId(request.getCategoryId());
recipe.setTitle(capitalizeFully(request.getTitle()));
recipe.setCategory(category);
recipeRepository.save(recipe);
}
Instead of retrieving category, I want to set the categoryId field of Recipe, but there is not such kind of setter:
recipe.setCategoryId(request.getCategoryId());
So, what is the most proper way for just setting the categoryId of the recipe and then saving it without requiring the category from db? Do I need a setter for categoryId field to the Recipe (I thought it, but does not seem elegant way)?
I would just add a categoryId field along with the corresponding getter and settter methods to the Recipe class.
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, length = 50)
private String title;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", referencedColumnName = "id")
private Category category;
#Column(name = "category_id", nullable = false)
private Integer categoryId;
// getters/setters
}
Having a categoryId field means that when we don't have to create an instance of Category when adding new Recipes. Sure, Recipe.category will be null but that's ok if we're just adding new Recipes. This approach could also prove beneficial if we later decide that we need to add many Recipes simultaneously.
If your repository implements org.springframework.data.jpa.repository.JpaRepository you may take advantage of using JpaRepository#getReferenceById method, in that case Hibernate instead of querying DB for data will return proxy object. However, such implementation may cause issues in some cases, for example:
// this call typically returns entity
// or null if entity wasn't found
repository.findById(id);
but:
// this call returns proxy object
repository.getReferenceById(id);
// now instead of returning entity
// repository either returns initialized proxy
// object or throws EntityNotFoundException
// if entity wasn't found
repository.findById(id);
I'll try to illustrate what I'm trying to achieve shortly...
Let's suppose I have a users table:
USER_INFO
USER_ID [PK]
USER_NAME
PASSWORD
an intersection table to define connections for each user (N:M - ManyToMany)
CONNECTION_INFO
CONNECTION_ID [PK]
USER_A_ID [FK - references USER_INFO(USER_ID)]
USER_B_ID [FK - references USER_INFO(USER_ID)]
CONNECTION_TYPE_ID [FK - references CONNECTION_TYPE(CONNECTION_TYPE_ID)]
The CONNECTION_TYPE is simple as:
CONNECTION_TYPE
CONNECTION_TYPE_ID [PK]
CONNECTION_TYPE_NAME [CHECK allowed values are: FRIEND, FAMILY, ...]
On Spring side I defined my User entity as:
#Entity
#Table(name = "USER_INFO")
public class User implements Serializable {
#Id
#NotNull
#Column(name = "USER_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer userId;
#Column(name = "USER_NAME)
private String userName;
#Column(name = "PASSWORD)
private char[] password;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "CONNECTION_INFO",
joinColumns = { #JoinColumn(name = "USER_A_ID") },
inverseJoinColumns = { #JoinColumn(name = "USER_B_ID") })
private List<User> connections;
// ctor, getters, setters, toString, ...
}
I have a UserRepository interface that extends JpaRepository etc etc. Now, this works perfectly and I can retrieve all connections be it FRIEND, FAMILY, MOST_HATED_PERSONS, BLOCKED, DEMON, etc...
I tried to integrate the ConnectionType too in the picture however...
#Entity
#Table(name = "CONNECTION_TYPE")
public class Connection implements Serializable {
public static enum Types {
FRIEND, FAMILY, BLOCKED, ...
}
#Id
#NotNull
#Column(name = "CONNECTION_TYPE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer connectionTypeId;
#Column(name = "CONNECTION_TYPE_NAME")
private ConnectionType connectionType;
// ctor, getters, setter, etc
}
Now, my question is, how can I get only specific connections for a given user, based on Connection.Types? For example I want to find only FRIENDs, or only FAMILY I think you get my point. This 3 way intersection table gives me one of a headache.
#Clarification:
What I want is a #ManyToMany relation defined on my User entity that happen to have extra column. I know in that case there are proposed solutions like LINK. In my case this extra column is a foreign key to a third table (USER_INFO(Holds the users), CONNECTION_INFO(Holds the connections between users N:M + an info on the type of connection), CONNECTION_TYPE. If I can model it with spring-data-jpa from what I understand I only need a magic named method under UserRepository, something like (totally incorrect):
public interface UserRepository extends JpaRepository<User, Integer> {
List<User> findUserFriendsByConnectionType(User userWhoseFriendsWeAreSearching, String connectionTypeFromTheThirdTable);
}
That's all I want. I know it's simple with a normal extra column by creating an entity for the intersection table too and break the ManyToMany to OneToMany and ManyToOne, it just happens I have a third table and a possibly ManyToOne (1 connection can have 1 associated type, while a type can be linked to any number of connections) on the intersection entity with the connection_type table.
I hope it clears everything up. The above are just a sample I never imagined we'd hang up on an enum because I wanted to make it look simple I possibly made it way too simple perhaps :).
I managed to solve the problem but I'm not sure if this is the right way to do it. Anyway here's my solution. Consider the following 3 tables:
create table USER_INFO (
USER_ID int not null primary key,
USER_NAME varchar(16),
PASSWORD varchar(64)
);
create table CONNECTION_TYPE (
CONNECTION_TYPE_ID int not null primary key,
CONNECTION_TYPE_NAME varchar(16) not null,
CONNECTION_TYPE_DESCRIPTION varchar(128),
unique (CONNECTION_TYPE_NAME)
);
create table CONNECTION (
CONNECTION_ID int not null primary key,
CONNECTION_TYPE_ID int,
RELATED_USER_ID int,
RELATING_USER_ID int,
foreign key (CONNECTION_TYPE_ID) references CONNECTION_TYPE(CONNECTION_TYPE_ID),
foreign key (RELATED_USER_ID) references USER_INFO(USER_ID),
foreign key (RELATING_USER_ID) references USER_INFO(USER_ID)
With the above 3 tables, I want to provide a functionality to get connections for any given user based on the connection's type. For this I created 3 entities as follows:
#Entity
#Table(name = "CONNECTION_TYPE")
public class ConnectionType implements Serializable {
#Id
#NotNull
#Column(name = "CONNECTION_TYPE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer connectionTypeId;
#NotNull
#Column(name = "CONNECTION_TYPE_NAME", unique = true)
private String connectionTypeName;
#Column(name = "CONNECTION_TYPE_DESCRIPTION")
private String connectionTypeDescription;
...
}
Nothing particularly interesting in here, I omitted the constructor, getters, setters etc and from the ConnectionType I don't want to have a mapping for all connections for this type so that direction is not present.
#Entity
#Table(name = "CONNECTION")
public class Connection implements Serializable {
#Id
#NotNull
#Column(name = "CONNECTION_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer connectionId;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CONNECTION_TYPE_ID", referencedColumnName = "CONNECTION_TYPE_ID")
private ConnectionType connectionType;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "RELATED_USER_ID", referencedColumnName = "USER_ID")
private User relatedUser;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "RELATING_USER_ID", referencedColumnName = "USER_ID")
private User relatingUser;
...
}
This one is more interesting if for noone else at least for me. This would be my intersection table entity. There's the uni-directional mapping for the used ConnectionType with ManyToOne as one Connection can have exactly one ConnectionType while the same ConnectionType can be reused for an arbitrary number of Connections.
The other 2 User mappings I'm sure I've messed up but before that here's the User entity:
#Entity
#Table(name = "USER_INFO")
public class User implements Serializable {
#Id
#NotNull
#Column(name = "USER_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer userId;
#NotNull
#Column(name = "USER_NAME")
private String userName;
#NotNull
#Column(name = "PASSWORD")
private char[] password;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "relatedUser", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Connection> connections;
}
Now here I'm even more sure I completely messed up, but I'll show the actual error. My repository is simple as a brick:
#Repository
public interface UserRepository extends JpaRepository<User, Integer> {
}
And I have a UserService with a simplified findAllConnectionsForUserById function:
#Service
public interface UserService {
List<User> findAllConnectionsForUserById(Integer userId);
}
The method implementation is simple enough:
#Override
#Transactional
public List<User> findAllConnectionsForUserById(Integer userId) {
Optional<User> _user = userRepository.findById(userId);
// omitted exception handling...
User user = _user.get();
List<Connection> connections = user.getConnections();
return connections.strea.map(Connection::getRelatingUser).collect(Collectors.toList());
This way it seem to work fine for the simple case and in case I take the ConnectionType too:
connections.stream().filter(c -> c.getConnectionType().getConnectionTypeName().equals("FRIEND")).map(Connection::getRelatingUser).collect(Collectors.toList());
it seem to work as well. Again, not sure if this is the right way but at least it does the job.
When trying to insert an Entity - Awith a set Another Entity B, B should get the Auto generated Id from A but its null.
Tried and failed:
#MapsId("taskPKId.storyId.id") - Same error.
#Embeddable
class StoryId {
#ManyToOne(fetch = FetchType.Lazy)
#JoinColumn(name = "STORY_ID")
Long id;
} //Incomprehensible Null pointer exception
mappedBy("story") - same error
Tried with mappedBy('story') but getting an error with repeated column and so had to map it with insertable=false and updatable=false [Hibernate doesn't recognize insertable=false for #EmbeddedId]
I am getting STORY_ID = null and therefore saveAll fails on storyRepository.saveAll(stories) where storyRepository is a Spring Data repository
#Table(name = "STORY")
#EqualsAndHashCode
class Story {
#Id
#GeneratedValue(stratergy=GenerationType.Auto)
#Column(name="STORY_ID")
Long id;
#Column(name="STORY_NAME")
String name;
//#OneToMany(cascade=ALL, mappedBy="taskPKId.storyId.id", fetch = FetchType.Lazy) // tried this as well
#OneToMany(cascade=ALL, mappedBy="story", fetch = FetchType.Lazy)
Set<Task> task;
}
#Table(name = "TASK_XREF")
#EqualsAndHashCode
Class Task {
#EmbeddedId
TaskPKId taskPKId;
#Column(name = "TASK_NAME")
String name;
#ManyToOne (fetch = FetchType.Lazy, optional = false)
#JoinColumn(name = "STORY_ID", referencedColumnName = "STORY_ID", nullable = false, insertable = false, updatable = false)
Story story;
}
#Embeddable
#EqualsAndHashCode
Class TaskPKId implements Serializable {
TaskId taskId;
TaskTypeId taskTypeId;
StoryId storyId;
}
#Embeddable
#EqualsAndHashCode
class StoryId implements Serializable {
#Column(name = "STORY_ID")
Long id;
}
Tables:
STORY [STORY_ID, STORY_NAME]
TASK_XREF [(TASK_ID(FK), TASK_TYPE_ID(FK), STORY_ID(FK)) PK,TASK_NAME]
Story gets inserted (before commit ofcourse), but fails because STORY_ID is sent as null to TASK_XREF for the next inserts
I'm not quite sure why your configuration does not work. I have a similar configuration in one of my projects that works just fine. I was able to find a solution however, by adding a #MapsId annotation to the ManyToOne in the Task class. (see can someone please explain me #MapsId in hibernate? for an explanation about MapsId) I also removed insertable=false and updatable=false. See below for the code.
I didn't get MapsId to work with the StoryId class, so i changed the type of TaskPKID.storyId from StoryId to long. The StoryId class doesn't seem to add much, so hopefully this isn't to much of a problem. If you find a solution please let me know in the comments though!
By the way, your code has a lot of problems. There's a bunch of typo's, and there is a OneToMany mapping on a property that is not a Collection (which isn't allowed) This made it more difficult for me to debug the problem. Please make sure to post better quality code in your questions next time.
Here is the Task class the way I implemented it:
#Entity
#Table(name = "TASK_XREF")
class Task {
#EmbeddedId
TaskPKId taskPKId;
#Column(name = "TASK_NAME")
String name;
#MapsId("storyId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "STORY_ID")
Story story;
//getters, setters
}
And here is the TaskPKID class:
#Embeddable
class TaskPKId implements Serializable {
long taskId;
long taskTypeId;
#Column(name="STORY_ID")
long storyId;
public long getTaskId() {
return taskId;
}
public void setTaskId(long taskId) {
this.taskId = taskId;
}
public void setTaskTypeId(long taskTypeId) {
this.taskTypeId = taskTypeId;
}
}
I'm not sure what you want to achieve, but it looks like you have combined #OneToMany annotation with #OneToOne-like implementation (which can lead to unexpected behavior like this one).
Possible solutions:
If one story owns multiple tasks
// Story.java
#OneToMany(cascade=ALL, mappedBy="story", fetch = FetchType.Lazy)
Set<Task> task; // basically Set, List or any other collection type
// Task.java
#ManyToOne (fetch = FetchType.Lazy, optional = false)
#JoinColumn(name = "STORY_ID", referencedColumnName = "STORY_ID")
Story story;
If one story owns only one task
// Story.java
#OneToOne(cascade=ALL, mappedBy="story", fetch = FetchType.Lazy)
Task task;
// Task.java
#OneToOne (fetch = FetchType.Lazy, optional = false)
#JoinColumn(name = "STORY_ID", referencedColumnName = "STORY_ID")
Story story;
Further reading:
#OneToOne
#OneToMany
Many times I'm using #Formula in my entities. But always it was a simple query or stored procedure with parameter which I can take as filed from table. Now I need to user some property from related object. But I see exception when try to get object from DB. Please see an example below
#Entity
#Table(name = "MINISTRY")
public class Ministry {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
// unnecessary code
}
#Entity
#Table(name = "DEPARTMENT")
public class Department {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "DEP_NAME")
private String departmentName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "MINISTRY_ID")
private Ministry ministry;
// unnecessary code
}
#Entity
#Table(name = "EMPLOYEE")
public class Employee {
#Id
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DEPARTMENT_ID")
private Department department;
#Formula("test_package.calc_something(department.ministry.id)")
private BigDecimal someMetric;
// unnecessary code
}
How I should use entity prop in #Formula.
I don't want to write something like
select d.ministry.id from Department d ...
If you read the JavaDoc of Formula you will see:
The formula has to be a valid SQL fragment
So you will have to use SQL like:
#Formula("test_package.calc_something("
+ "select DEP.MINISTRY_ID from DEPARTMENT DEP where DEP.ID = DEPARTMENT_ID"
+ ")")
private BigDecimal someMetric;
The only thing that is modified by Hibernate in the fragment before writing it to SQL: It will add the table alias to your columns (as you can't predict that). I mention that, as only a rudimentary SQL parser is used for that, which will insert the alias at wrong positions for more complex fragments.
A remark about performance: The formula is executed for every Department entity that you load, even if you only want to use the attribute for sorting or filtering (just guessing from the name of the attribute) - unless you use #Basic(fetch = FetchType.LAZY) and turn bytecode instrumentation on (or emulate that with FieldHandled).
Firstly, I am somewhat new with Hibernate. To get to know the technology I am using it in a project. I am trying to map the following database:
Campaign
campaignId(+)
name
Promotion
campaignId(+)
discount(+)
product
message
I've indicated the primary key in both cases with a (+). The 'campaignId' in Promotion is a foreign key to Campaign to model the 1:m mapping (A Campaign has many Promotions). Using annotations I am stuck on how to do this.
I do not really want to add a promotionId in the Promotion table as it makes working with the data cumbersome. This of course, makes the bridging table a bit tricky. I also have problems working with a foreign key that is also part of the primary key.
Is a mapping for this possible at all?
Ok, I got it working. Sort of. Have to check if persistence actually work. I did the following:
#Entity
#Table(name = "CAMPAIGNS")
#Audited
public class CampaignEntity {
private int campaignId;
private String name;
private List<PromotionEntity> promotions;
public CampaignEntity(int campaignId, String name) {
this.campaignId = campaignId;
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "cmp_id")
public int getCampaignId() {
return campaignId;
}
public void setCampaignId(int campaignId) {
this.campaignId = campaignId;
}
// Campaign name here... left out to save space
#OneToMany
#JoinColumn(name = "cmp_id")
public List<PromotionEntity> getPromotions() {
return promotions;
}
public void setPromotions(List<PromotionEntity> promotions) {
this.promotions = promotions;
}
}
Promotion is a vanilla mapping (not using embedded after all), with the fields: campaignId, discount, message. (It also does not have a #ManyToOne annotation.)
Does that make sense?
Lastly, and this will be first prize: as you can see I'm using Envers to audit the whole thing. The above creates a rather ugly "CampaignEntity_PromotionEntity_AUD" table. I understand that it is needed, but how can I rename it to CAMPAIGN_PROMOTION_AUD rather?
Thanks guys!
I got an answer on a lonely website deeply hidden away in far-corners of the Hibernate's Jira error tracking website: https://hibernate.onjira.com/browse/HHH-3729.
The answer is to use #AuditJoinTable(name = "CAMPAIGN_PROMOTION_AUD") of course.
This is a basic example of a one-to-many relationship and its inverse.
public class Campaign
{
#OneToMany(mappedBy = "campaign)
private List<Promotion> promotions;
}
public class Promotion
{
#ManyToOne
private Campaign campaign;
}
You can use an EmbeddedId to create a multi-field PK.
Remove the PK fields from Promotion
Create a separate entity, say PromotionPK, without any annotations except for #Column on the PK fields
In Promotion, include that PK class as field, annotating it using #EmbeddedId, with getters and setters
The FK mapping is as Wouter indicated.
This is what I am now using. It works well and Hibernate handles the PKs of the Promotions for me. Thanks again.
#Entity
#Table(name = "CAMPAIGNS")
#Audited
public class CampaignEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer campaignId;
#Column(name = "name", nullable = false, unique = true)
private String campaignName;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "CAMPAIGN_PROMOTIONS",
joinColumns = { #JoinColumn(name = "campaign_id") },
inverseJoinColumns = { #JoinColumn(name = "promotion_id") })
private Set<PromotionEntity> promotions;
...
}
and then, PromotionEntity:
#Entity
#Table(name = "PROMOTIONS")
#Audited
public class PromotionEntity implements Comparable<PromotionEntity> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "discount", nullable = false)
private Integer discount;
#Column(name = "message", nullable = false)
private String message;
...
}
I also prefer annotating the fields rather than the getters as it is more compact and reads easier.