#Column insertable, updateble don't go well with Spring JPA? - java

Scenario :
I have 3 tables, Offer, Channel and Offer_Channels.
Basically Channel is a lookup table, i.e, the values in that table can neither be inserted nor updated by the application. An offer can contain one or many channels. I use the Channel table values to populate dynamic checkboxes. Anyways, so here is what I have :
#Entity
#Table(name = "OFFER")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Offer implements Serializable {
// Offer Id
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "offer_seq_gen")
#Column(name = "OFFER_ID")
private long OfferId;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "OFFER_CHANNELS", joinColumns = { #JoinColumn(name = "OFFER_ID") }, inverseJoinColumns = { #JoinColumn(name = "CHANNEL_ID") })
private Set<Channel> channels = new HashSet<Channel>();
//Other fields and corresponding getters and setters
}
Here is the Channel entity :
#Entity
#Table(name = "CHANNEL")
public class Channel implements Serializable {
private static final long serialVersionUID = 1L;
#NotNull
#Id
#Column(name = "CHANNEL_ID", insertable=false, updatable=false)
private Long channelId;
#Column(name = "CHANNEL_NAME", insertable=false, updatable=false)
private String channelName;
//getters and setters
}
Now, when a user creates an offer, I need to insert row in Offer table and Offer_Channels tables and do nothing(No updates/inserts) for Channel table. Initially, all three would happen, so to achive the "do nothing to Channel table" part, I put insertable=false and updateable=false on the Channel table columns and that worked like a charm. Now I used plain Hibernates for this. I mean I wrote a standalone java application and a main class to add an offer useing hibernate's session.save(offer). It ran the following queries :
Hibernate: insert into OFFER
Hibernate: insert into OFFER_CHANNELS
Alright, now, I have a rest service where I am using the Spring's JPA repository to save the information and I have the same domain objects setup. Now, when I add an offer, it runs :
Hibernate: insert into OFFER
Hibernate: insert into CHANNEL ( It is failing here obviously. I don't want this step to happen)
My question :
1. Why is it is trying to write something to Channel table even though I gave insertable=false in its domain object, and this is only happening with the Spring JPA setup. With the hibernate setup it just works fine.
3. Doesn't #JoinTable/ #OneToMany / insertable / updateble , go well with Spring JPA repository ?
What am I missing here ?
UPDATE :
#Service
#Transactional
public class OfferService {
#Inject
private OfferRepository offerRepository;
public Offer saveOfferInformation(Offer offer) {
log.debug("Saving Offer Info..");
log.debug("Offer object :"+offer);
return offerRepository.save(offer);
}
}
Repo :
public interface OfferRepository extends JpaRepository<Offer, Long> {
List<Offer> findByBuySku(String buySku);
}
And in the REST service Im just injecting the service and calling it, so no business logic in the REST service. Right now Im getting and the reason is it is trying to insert record to Channel table:
exception: "org.springframework.dao.DataIntegrityViolationException"
message: "could not execute statement; SQL [n/a]; constraint [PVS_OWNER.CHANNEL_PK]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement"

Have you tried to add insertable and updatable on the #JoinColumn. This works with One to Many relationships. I'm not 100% sure if it works with a Many to Many relationship.
#JoinTable(name = "OFFER_CHANNELS", joinColumns = { #JoinColumn(name = "OFFER_ID", insertable = false, updatable = false ) }, inverseJoinColumns = { #JoinColumn(name = "CHANNEL_ID", insertable = false, updatable = false ) })

Related

JPA good practices mapping table

Im trying to apply the best practices to my JPA mapping table but i have a question about it, this is my table map:
#Entity
#Table(name = "TW_TABLE")
public class TwTable {
#Id
#Column(name = "N_ID")
private Long nId;
#Column(name = "N_IDCATALOGE")
private Long nIdCataloge;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "N_IDCATALOGE", insertable = false, updatable = false)
private TcCataloge tcCataloge;
}
this is my entity i have more columns and i have my getters and setters but i dont need them here, my questions is about the column N_IDCATALOGE, some querys only need the ID of the cataloge but some others will need the complete entity of tcCataloge, is it a good practice have both on the entity or should i delete the single column nIdCataloge and use the object to get the ID (on some cases i will only need the ID not the full object)?

JPA ManyToMany to use grouping & crosswalks to join data together

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.

Spring Data: How to write a record in join table for unidirectional one-to-may?

I have a Subscription class and Payment class. When I do the following, it doesn't create a record in join table. Should I use intermediate class or is it possible to create such record without it? subscriptionRepository is a CrudRepository from Spring-Data.
#Transactional
public Subscription activate(#Valid Subscription subscription, #Valid Payment payment) {
Set<Payment> payments = subscription.getPayments();
if (payments == null)
payments = new HashSet<>();
payments.add(payment);
return subscriptionRepository.save(subscription);
}
Classes:
Subscription:
#Entity
public class Subscription {
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
joinColumns = {#JoinColumn(name = "subscription_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "payment_id", referencedColumnName = "id", unique = true)}
)
#Getter #Setter
private Set<Payment> payments;
}
Payment:
#Entity
public class Payment {
#Column
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#JsonIgnore
private Integer id;
#Column(nullable = false)
private PaymentType paymentType;
#Past
#Column(nullable = false)
private Date date;
public enum PaymentType {
MONEY,
PROMO_CODE,
TRIAL
}
}
you forgot to inject the payments in the subcription , your repository and pojo seem just fine
if (payments == null) {
payments = new HashSet<>();
subscription.setPayments(payments);
}
First of all, you need to mark your method with #Transactional annotation, cause the Spring Data save method does not execute explicit save action, it just selects a database row identifier and sets it to your entity.
1) Mark your method as #Transactional (best solution)
2) Inject EntityManager and create a transaction manually.
P.S.: JPA Persistence with Hibernate advises to initialize your collections in your model class (No lazy initialization). It reduces a lot of boilerplate checks and sometimes the realization shows Hibernate which Hibernate built-in collection to use (bags etc)

OpenJPA - Nested OneToMany relationships merge issue

Posting this here as I wasn't seeing much interest here: http://www.java-forums.org/jpa/96175-openjpa-one-many-within-one-many-merge-problems.html
Trying to figure out if this is a problem with OpenJPA or something I may be doing wrong...
I'm facing a problem when trying to use OpenJPA to update an Entity that contains a One to Many relationship to another Entity, that has a One to Many relationship to another. Here's a quick example of what I'm talking about:
#Entity
#Table(name = "school")
public class School {
#Column(name = "id")
protected Long id;
#Column(name = "name")
protected String name;
#OneToMany(mappedBy = "school", orphanRemoval = true, cascade = CascadeType.ALL)
protected Collection<ClassRoom> classRooms;
}
#Entity
#Table(name = "classroom")
public class ClassRoom {
#Column(name = "id")
protected Long id;
#Column(name = "room_number")
protected String roomNumber;
#ManyToOne
#JoinColumn(name = "school_id")
protected School school;
#OneToMany(mappedBy = "classRoom", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
protected Collection<Desk> desks;
}
#Entity
#Table(name = "desk")
public class Desk {
#Column(name = "id")
protected Long id;
#ManyToOne
#JoinColumn(name = "classroom_id")
protected ClassRoom classRoom;
}
In the SchoolService class, I have the following update method:
#Transactional
public void update(School school) {
em.merge(school);
}
I'm trying to remove a Class Room from the School. I remove it from the classRooms collection and call update. I'm noticing if the Class Room has no desks, there are no issues. But if the Class Room has desks, it throws a constraint error as it seems to try to delete the Class Room first, then the Desks. (There is a foreign key constraint for the classroom_id column)
Am I going about this the wrong way? Is there some setting I'm missing to get it to delete the interior "Desk" instances first before deleting the Class Room instance that was removed?
Any help would be appreciated. If you need any more info, please just let me know.
Thanks,
There are various bug reports around FK violations in OpenJPA when cascading remove operations to child entities:
The OpenJPA FAQ notes that the following:
http://openjpa.apache.org/faq.html#reorder
Can OpenJPA reorder SQL statements to satisfy database foreign key
constraints?
Yes. OpenJPA can reorder and/or batch the SQL statements using
different configurable strategies. The default strategy is capable of
reordering the SQL statements to satisfy foreign key constraints.
However ,you must tell OpenJPA to read the existing foreign key
information from the database schema:
It would seem you can force the correct ordering of the statements by either setting the following property in your OpenJPA config
<property name="openjpa.jdbc.SchemaFactory"> value="native(ForeignKeys=true)"/>
or by adding the org.apache.openjpa.persistence.jdbc.ForeignKey annotation to the mapping:
#OneToMany(mappedBy = "classRoom", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#org.apache.openjpa.persistence.jdbc.ForeignKey
protected Collection<Desk> desks;
See also:
https://issues.apache.org/jira/browse/OPENJPA-1936

Hibernate Spring: #ManyToMany DataIntegrityViolationException ConstraintViolationException

I am building a sample for ManyToMany relationship between: User(1) - ()AccessLevel() - (1)Role
I have implemented 3 classes in Java with hibernate implementation as follow:
Class User
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue
#Column(name="USER_ID")
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "access_level", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id", nullable = false, updatable = false) })
private Set<Role> roles = new HashSet<Role>(0);
Class Role
#Entity
#Table(name="role")
public class Role {
#Id
#GeneratedValue
#Column(name="role_id")
private Integer id;
#Column(name="role_name")
private String roleName;
Class AccessLevel
#Entity
#Table(name="access_level")
public class AccessLevel {
#Id
#GeneratedValue
private Integer id;
#Column(name="role_id")
private Integer roleId;
#Column(name="user_id")
private Integer userId;
Problem:
When I am persisting the User bean using merge method then an exception arise:
#Service
public class UserServiceImpl implements UserService {
#PersistenceContext
private EntityManager em;
#Override
#Transactional
public void save(User user) {
em.merge(user);
}
Exception
org.springframework.web.util.NestedServletException: Request process
ing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [insert into access_level (user_id, role_id) values (?, ?)]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:894)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
As you can see hibernate is trying to run this query:
insert into access_level (user_id, role_id) values (?, ?)
From my point of view it seems like hibernate is not generating the primary key for AccessLevel even though I have added the #GeneratedValue to the id attribute.
Note:
I am working on production environment with Postgresql and evelopment environment with HSQL database that creates all schemas from the begining based on the entities description. Both environments generate same issue.
Regards,
Cristian Colorado
Reason:
It seems for ManyToMany relationships you do not need to define a class for the "Joining Table". Therefore if I eliminate AccessLevel entity the logic would work perfectly fine. I explain further:
Explanation:
When I defined the User class I also described the relationship with Role:
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "access_level", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id", nullable = false, updatable = false) })
private Set<Role> roles = new HashSet<Role>(0);
Important thing here is I have told hibernate that User entity will relate to Role entity through a table known as "access_level" and such table will have user_id and role_id columns in order to join previous entities.
So far this is all hibernate needs in order to work the many to many relationship, therefore when mergin it uses that information to create and tun this script:
insert into access_level (user_id, role_id) values (?, ?)
Now, the problem cames when I defined a new entity for AccessLevel:
#Entity
#Table(name="access_level")
public class AccessLevel {
#Id
#GeneratedValue
private Integer id;
#Column(name="role_id")
private Integer roleId;
#Column(name="user_id")
private Integer userId;
Now I am telling hibernate that there is a table "access_level" related to AccessLevel entity and it has 3 columns, the most important would be Id which is primary key.
So I defined "access_level" twice!
Solution:
I eliminated the Entity for access_level table.
I re-write my production script in order to have "access_level" with
user_id/role_id columns only.
Note: It would be good to know how to add a primary key to the joining table without generating issues. An alternative would be adding a composed primary key in database(user_id/role_id) which would be independient from hibernate.
Why do you need a PK column in the join table? There will be a composite PK composed of user_id and role_id. Now, as you have discovered a JoinTable for #ManyToMany will only ever have two columns and at some point you may require additional data about this relationship.
e.g.
user_id
role_id
date_granted
You may then want to use your AccessLevel entity however you replace the #ManyToMany with #OneToMany from User to AccessLevel and optionally from Role > AccessLevel.
The Hibernate docs themselves advise against #ManyToMany:
Do not use exotic association mappings:
Practical test cases for real many-to-many associations are rare. Most
of the time you need additional information stored in the "link
table". In this case, it is much better to use two one-to-many
associations to an intermediate link class. In fact, most associations
are one-to-many and many-to-one. For this reason, you should proceed
cautiously when using any other association style.

Categories

Resources