Data is reinserted in the table on using #ElementCollection on Set<String> - java

I am using JPA 2.0 in my project and I have used #ElementCollection on one of my property in the Pojo class.
Following is my java entity class :
#Entity
#Embeddable
#Table(name = "test")
public class Test {
#Id
#GeneratedValue(generator = "seq_test")
#SequenceGenerator(name = "seq_test", sequenceName = "seq_test")
#Column(name = "id")
private Long id;
#ElementCollection(fetch = FetchType.LAZY, targetClass = String.class)
#CollectionTable(name = "denied_set")
#OrderColumn
private Set<String> deniedSet;
#ElementCollection(fetch = FetchType.LAZY, targetClass = String.class)
#CollectionTable(name = "masked_set")
#OrderColumn
private Set<String> maskedSet;
#ElementCollection(fetch = FetchType.LAZY, targetClass = String.class)
#CollectionTable(name = "user_groups")
#OrderColumn
private Set<String> userGroups;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<String> getDeniedSet() {
return deniedSet;
}
public void setDeniedSet(Set<String> deniedSet) {
this.deniedSet = deniedSet;
}
public Set<String> getMaskedSet() {
return maskedSet;
}
public void setMaskedSet(Set<String> maskedSet) {
this.maskedSet = maskedSet;
}
public Set<String> getUserGroups() {
return userGroups;
}
public void setUserGroups(Set<String> userGroups) {
this.userGroups = userGroups;
}
}
I am using Spring Data JPA Repositories for interaction with database. Data is properly inserted into the database table. When I delete some data from the table, I can see in the logs that the delete queries are executed on the child tables also and after that the same data is reinserted into the table.
Can anyone help me understand if I am missing anything in the code ?
Thanks in advance.

Just some questions that might help you:
Do you need #Embedded public class Test? If you are not using it in
another owning entity then remove it.
Specify which column should be used as id for the
#ElementCollection. Use something like this:
#CollectionTable(name="denied_set",
JoinColumns=#JoinColumn(name="COLUMN_NAME")) or use Embedded if the
deniad_set is Embeddable.
Fetch type is lazy by default on ElementCollection and do you really need targetClass there?
Otherwise it looks correct to me...

Related

SQl error - Referential integrity constraint violation when persisting several entities at once

Problem I'm trying to solve
I'm trying to model a #ManyToMany relation between a User and Role, such that a user can have n roles, and one role is referenced by several users. A role can be persisted even if it's not referenced by any user (detached), and a user with no roles is allowed too.
The same kind of relation must be built between Role and ResourcePermission.
To give you an idea about how each entity looks like:
Both ResourcePermission and Role have a finite set of values. For example, if Patient happens to be a resource, then one resource permission could be "PATIENT:READ" or "PATIENT:WRITE", and the role DOCTOR has several of these permissions. I hope it's clear sofar how my data model looks like.
What I'm using
Currently, I'm using spring-data-jpa version 2.4.2 to model my entities, and to create my CRUD repos. Except for base path and media type, I don't have any specific configuration (all is set to default).
Hibernate is my persistence provider atm .
Concerning my datasource, I'm using in-memory H2 for my development environment, and again no specific configuration there either.
How I'm solving it
Here's how my entities look like
User.java
#Table
#Entity
#Data
public class User implements Serializable {
private static final long serialVersionUID = 1123146940559321847L;
#Id
#GeneratedValue(generator = "user-id-generator")
#GenericGenerator(name = "user-id-generator",
strategy = "....security.entity.UserIdGenerator",
parameters = #Parameter(name = "prefix", value = "USER-")
)
#Column(unique = true, nullable = false)
private String id;
#Column
private int age;
#Column(unique = true, nullable = false)
private String username;
#Column(unique = false, nullable = false)
private String password;
#ManyToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.MERGE
)
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private List<Role> roles = Collections.emptyList();
public User withId(final String id) {
this.id = id;
return this;
}
public User withAge(final int age) {
this.age = age;
return this;
}
public User withUsername(final String username) {
this.username = username;
return this;
}
public User withPassword(final String password) {
this.password = password;
return this;
}
public User withRoles(final Role... roles) {
return withRoles(Arrays.stream(roles).collect(Collectors.toList()));
}
public User withRoles(final List<Role> roles) {
this.roles = roles;
return this;
}
}
Role.java
#Data
#NoArgsConstructor
#Table
#Entity
public class Role implements Serializable {
private static final long serialVersionUID = 812344454009121807L;
#Id
private String roleName;
#ManyToMany(
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
#JoinTable(
name = "role_resource_permission",
joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "resource_permission_id")
)
private Set<ResourcePermission> resourcePermissions = Collections.emptySet();
#ManyToMany(
mappedBy = "roles",
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
private List<User> users = Collections.emptyList();
public Role(final String roleName) {
setRoleName(roleName);
}
public void setRoleName(final String roleName) {
final RoleType roleType = RoleType.of(roleName);
this.roleName = roleType.getRoleName();
final Set<ResourcePermission> resourcePermissions = roleType.getResourcePermissions().stream()
.map(ResourcePermissionType::getPermissionName)
.map(ResourcePermission::new)
.collect(Collectors.toSet());
setResourcePermissions(resourcePermissions);
}
public void setResourcePermissions(final Set<ResourcePermission> resourcePermissions) {
if (this.resourcePermissions.isEmpty()) {
this.resourcePermissions = resourcePermissions;
}
}
}
ResourcePermission.java
#NoArgsConstructor
#Data
#Table
#Entity
public class ResourcePermission implements Serializable {
private static final long serialVersionUID = 883231454000721867L;
#Id
private String permissionName;
public ResourcePermission(final String permissionName) {
setPermissionName(permissionName);
}
#ManyToMany(
mappedBy = "resourcePermissions",
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
private Set<Role> roles = Collections.emptySet();
public void setPermissionName(String permissionName) {
final ResourcePermissionType permissionType = ResourcePermissionType.of(permissionName);
this.permissionName = permissionType.getPermissionName();
}
}
RoleType.java
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum RoleType {
DOCTOR("DOCTOR", doctorsPermissions()),
TECHNICIAN("TECHNICIAN", technicianPermission()),
ADMIN("ADMIN", adminPermissions());
#Getter
private String roleName;
#Getter
private final List<ResourcePermissionType> resourcePermissions;
public static RoleType of(final String roleName) {
return Arrays.stream(values())
.filter(roleType -> roleType.getRoleName().equals(roleName.toUpperCase()))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
private static List<ResourcePermissionType> doctorsPermissions() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
PATIENT_READ, PATIENT_WRITE
);
}
private static List<ResourcePermissionType> adminPermissions() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
BUILDING_UNIT_READ, BUILDING_UNIT_WRITE,
ORG_UNIT_READ, ORG_UNIT_WRITE
);
}
private static List<ResourcePermissionType> technicianPermission() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
BUILDING_UNIT_READ, BUILDING_UNIT_WRITE
);
}
}
ResourcePermissoinType.java
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ResourcePermissionType implements Serializable {
PATIENT_READ("PATIENT:READ"), PATIENT_WRITE("PATIENT:WRITE"),
ENCOUNTER_READ("ENCOUNTER:READ"), ENCOUNTER_WRITE("ENCOUNTER:WRITE"),
BUILDING_UNIT_READ("BUILDING_UNIT:READ"), BUILDING_UNIT_WRITE("BUILDING_UNIT:WRITE"),
ORG_UNIT_READ("ORG_UNIT:READ"), ORG_UNIT_WRITE("ORG_UNIT:WRITE");
#Getter
private String permissionName;
public static ResourcePermissionType of(final String permissionName) {
return Arrays.stream(values())
.filter(v -> v.getPermissionName().equals((permissionName.toUpperCase())))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
Unfortunately, the javax persistence API does not accept enums as entities. I tried using #Embeddable and #IdClass too, but that didn't work out for me either. I was not able to generate the schema that I had in mind. On the other hand, the schema was successfully generated using this model.
At the moment, both the Role repository as well as the Resource Permission repository are not exported (#RepositoryRestResource(..., exported = false)), so in order for you to persist those two entities, you'd have to provide that data in User. Keep that in mind, because that's also a part of the discussion that I want to talk about.
Now let's examine this integration test for the UserCrudRepository that will attempt to add a new user after a successful authentication.
#TestMethodOrder(OrderAnnotation.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
class UserCrudRepositoryApiITest {
private final List<User> testUsers = Arrays.asList(
new User().withUsername("dummy_username_01").withPassword("dummy_password_01").withAge(35)
.withRoles(new Role("ADMIN")),
new User().withUsername("dummy_username_02").withPassword("dummy_password_02").withAge(40)
.withRoles(new Role("DOCTOR")),
new User().withUsername("dummy_username_03").withPassword("dummy_password_03").withAge(45)
);
.
.
#Order(1)
#Test
public void afterAuthenticationAddNewUser() throws Exception {
final String generatedToken = login();
// serialize the user
final String requestJson = objectMapper.writeValueAsString(testUsers.get(0));
final RequestBuilder request = MockMvcRequestBuilders.post(USER_CRUD_BASE_URL)
.header(HttpHeaders.AUTHORIZATION, generatedToken)
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson);
final String serializedContent = mvc.perform(request)
.andExpect(status().isCreated())
.andReturn()
.getResponse()
.getContentAsString();
final User storedUser = objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(serializedContent, User.class);
assertThat(storedUser).isEqualTo(testUsers.get(0));
}
.
.
}
In here, I'm getting a status code conflict 409, and not able to persist all entities at once.
Unfortunately, SO allows only 30000 character, so please navigate to this repo if you would like to take a look at the log.
My Questions
I couldn't for the life of me understand where that referential integrity constraint violation
is occurring. Any idea?
Any suggestions on how to model these relations in a better way are welcome!
Another problem I'm having with JPA repos is that the only way to persist roles and resource permissions is by providing that data in the user's body. I would like those entities to be managed independently of the user (each with its own separate repository), so I tried exporting their repositories. However, the problem then is that you no longer can pass Role data in the body of a User, but rather A reference to that entity. Is there a way to get the best of both worlds.
I hope I made my problem clear, if not, I'd be happy to elaborate more.
I guess when a User is persisted, it also does the insert for the user_role table, but the role wasn't persisted yet. You could try to persist the Role first or use PERSIST cascading at the User#roles association.

Problem with #ManyToOne map in EclipseLink

I'm having trouble with this #ManyToOne map, searched a lot, but still can't find a solution for this problem.
I have these two classes, i will never insert anything into TB_MANUAL, i'll just use it as reference for the CD_MANUAL field in TB_COMPANY, like this:
Company company = new Company();
company.setManual("2"); //Theres already a row with this id in the TB_MANUAL
and then persist company, but i got this error:
Caused By: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: 2.
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.discoverUnregisteredNewObjects(RepeatableWriteUnitOfWork.java:313)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.calculateChanges(UnitOfWorkImpl.java:723)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1516)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.issueSQLbeforeCompletion(UnitOfWorkImpl.java:3168)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.issueSQLbeforeCompletion(RepeatableWriteUnitOfWork.java:355)
Truncated. see log file for complete stacktrace
-
#Entity
#Table(name = "TB_COMPANY", schema = "ADMPROD")
#Cacheable
public class Company implements Serializable {
private static final long serialVersionUID = 1L;
public Company() {}
public Company(String id) {
this.id = id;
}
#ManyToOne
#JoinColumn(name = "CD_MANUAL", referencedColumnName = "CD_MANUAL", nullable
= true)
private Manual manual;
public void setManual(String idManual) {
this.manual = new Manual(idManual);
}
}
and
#Entity
#Table(name = "TB_MANUAL")
public class Manual implements Serializable{
private static final long serialVersionUID = 1L;
public Manual() {
}
public Manual(String id) {
this.id = id;
}
#Id
#Column(name = "CD_MANUAL")
private String id;
#Column(name = "DS_OBS_MANUAL")
private String description;
}
You create new Manual every time you set it, so your object is detach from EntityManager, or has not data at all.
I don't argue if that is a good design (althought I've never would do it like that), to over come your problem you should add CascadeType.PERSIST to your relation.
#ManyToOne
#JoinColumn(name = "CD_MANUAL", referencedColumnName = "CD_MANUAL", nullable
= true, cascade = CascadeType.PERSIST)
private Manual manual;
The problem was in the Manual table primary key, the JPA doesnt find any row with id 1 because the primary key of Manual is char(2), passing "1 " instead of "1" solved the problem.

hibernate one-to-many updating skipped uniqueConstraints checking

I'm working on a project where I need to save a download file information with keywords.
Typical one-to-many scenario, right? A download record to many download-keyword relationship records.
So here is what the Download.java look like
#Entity
#Table(name = "downloads", catalog = "nbpx")
public class Download extends BaseEntity implements Serializable {
public Integer downloadId;
public String title;
public Set<DownloadKeyword> downloadKeywords = new HashSet<DownloadKeyword>(
0);
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "downloadId", unique = true, nullable = false)
public Integer getDownloadId() {
return downloadId;
}
public void setDownloadId(Integer downloadId) {
this.downloadId = downloadId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "download")
#Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
public Set<DownloadKeyword> getDownloadKeywords() {
return downloadKeywords;
}
public void setDownloadKeywords(Set<DownloadKeyword> downloadKeywords) {
this.downloadKeywords = downloadKeywords;
}
}
Since I don't want any duplicate download-keyword relation records in the table, I set a JPA unique Constraints for the class. But I didn't use a composite key. And this is what DownloadKeyword.java look like:
#Entity
#Table(name = "downloadkeywords", catalog = "nbpx", uniqueConstraints =
#UniqueConstraint(columnNames = {"keywordId", "downloadId" }))
public class DownloadKeyword extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
public Integer downloadKeywordId;
public Download download;
public Integer keywordId;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "downloadKeywordId", unique = true, nullable = false)
public Integer getDownloadKeywordId() {
return downloadKeywordId;
}
public void setDownloadKeywordId(Integer downloadKeywordId) {
this.downloadKeywordId = downloadKeywordId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "downloadId", nullable = false, insertable=true)
public Download getDownload() {
return download;
}
public void setDownload(Download download) {
this.download = download;
}
public Integer getKeywordId() {
return keywordId;
}
public void setKeywordId(Integer keywordId) {
this.keywordId = keywordId;
}
}
When I save a download entity, I expect to see all the download-keyword relationship entities are saved within the download entity save session. And I need to avoid duplication in download-keyword relationship table. But every time I save it, the relation records were inserted straightly into DB without checking the duplication with JPA annotation.
So here is my question: Did multiple-column uniqueConstraints of JPA annotation get ignored when entities were saving in one-to-many save or update session?
The uniqueConstraints are only for table generation support.
See the documentation.
Since you're already using Set<DownloadKeyword> in the Download class - you can implement hashCode and equals methods in the DownloadKeyword class (include primary key downloadKeywordId and the keywordId properties) to accomplish what you want.
However, I would still prefer a composite key approach.

Hibernate Exception: Trying to map a Collection OneToMany without JoinTable

I'm trying to map an entity the contains a Collection without use JoinTable.
The exception that I keep having is:
org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: Foo.collection[java.lang.String]
I know that it works with JoinTable, but then I get 2 tables while I want only one table. I know that if it worked then I'd have the "name" many times in the table (one for each element in the collection).
I looked in the documentation (http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#entity-mapping-association see section: 2.2.5.3.1.2. Unidirectional and http://docs.oracle.com/javaee/6/api/javax/persistence/OneToMany.html)and many examples but yet I cant find what I'm doing wrong :(
I also found many questions on this issue where the common answer is to use JoinTable, but this is not the solution I'm looking for.
I tries playing with it, like using #JoinColumn(name = "ID", referencedColumnName="NAME") but no good..
I'm using Java6, jpa2, hibernate 3.5.4. and MySql
Any advice?
Thanks in advance,
Baba
#Entity
#Table(name = "T1")
public class Foo {
private long id;
private String name;
protected Collection<String> collection;
#Id
#GeneratedValue
#Column(name = "ID")
public long getId() {
return id;
}
#Basic
#Column(name = "NAME", nullable = false, unique = true)
public String getName() {
return name;
}
#Column(name = "COLLECTION")
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "ID")
public Collection<String> getCollection() {
return collection;
}
/*
* setters...
*/
}
This is in Hibernate docs.
#ElementCollection
#CollectionTable(name="Nicknames", joinColumns=#JoinColumn(name="user_id"))
#Column(name="nickname")
public Set<String> getNicknames() { ... }

JPA not saving foreign key to #OneToMany relation

I'm using Spring with Hibernate as a JPA provider and are trying to get a #OneToMany (a contact having many phonenumbers) to save the foreign key in the phone numbers table. From my form i get a Contact object that have a list of Phone(numbers) in it. The Contact get persisted properly (Hibernate fetches an PK from the specified sequence). The list of Phone(numbers) also gets persisted with a correct PK, but there's no FK to the Contacts table.
public class Contact implements Serializable {
#OneToMany(mappedBy = "contactId", cascade = CascadeType.ALL, fetch=FetchType.EAGER)
private List<Phone> phoneList;
}
public class Phone implements Serializable {
#JoinColumn(name = "contact_id", referencedColumnName = "contact_id")
#ManyToOne
private Contact contactId;
}
#Repository("contactDao")
#Transactional(readOnly = true)
public class ContactDaoImpl implements ContactDao {
#Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void save(Contact c) {
em.persist(c);
em.flush();
}
}
#Controller
public class ContactController {
#RequestMapping(value = "/contact/new", method = RequestMethod.POST)
public ModelAndView newContact(Contact c) {
ModelAndView mv = new ModelAndView("contactForm");
contactDao.save(c);
mv.addObject("contact", c);
return mv;
}
}
Hopefully I got all of the relevant bits above, otherwise please let me know.
You have to manage the Java relationships yourself. For this kind of thing you need something like:
#Entity
public class Contact {
#Id
private Long id;
#OneToMany(cascade = CascadeType.PERSIST, mappedBy = "contact")
private List<Phone> phoneNumbers;
public void addPhone(PhoneNumber phone) {
if (phone != null) {
if (phoneNumbers == null) {
phoneNumbers = new ArrayList<Phone>();
}
phoneNumbers.add(phone);
phone.setContact(this);
}
}
...
}
#Entity
public class Phone {
#Id
private Long id;
#ManyToOne
private Contact contact;
...
}
In reply to Cletus' answer. I would say that it's important to have the #column annotation on the id fields, as well as all the sequence stuff. An alternative to using the mappedBy parameter of the #OneToMany annotation is to use the #JoinColumn annotation.
As a kinda aside your implementation of addPhone needs looking at. It should probably be something like.
public void addPhone(PhoneNumber phone) {
if (phone == null) {
return;
} else {
if (phoneNumbers == null) {
phoneNumbers = new ArrayList<Phone>();
}
phoneNumbers.add(phone);
phone.setContact(this);
}
}
If the Contact-Phone relationship is unidirectional, you can also replace mappedBy in #OneToMany annotation with #JoinColumn(name = "contact_id").
#Entity
public class Contact {
#Id
private Long id;
#OneToMany(cascade = CascadeType.PERSIST)
#JoinColumn(name = "contact_id")
private List<Phone> phoneNumbers;
// normal getter/setter
...
}
#Entity
public class PhoneNumber {
#Id
private Long id;
...
}
Similar in JPA #OneToMany -> Parent - Child Reference (Foreign Key)
I don't think the addPhone method is necessary, you only have to set the contact in the phone object:
phone.setContact(contact);
If you want your relationship unidirectional i.e. can navigate from Contact to Phone's only, you need to add
#JoinColumn(name = "contact_id", nullable = false)
Under your #OneToMany on your parent entity.
nullable = false IS VITAL if you want hibernate to populate the fk on the child table
Try this sample:
#Entity
public class Contact {
#Id
private Long id;
#JoinColumn(name = "contactId")
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Phone> phones;
}
#Entity
public class Phone {
#Id
private Long id;
private Long contactId;
}
In JPA this helped me
contact.getPhoneList().forEach(pl -> pl.setContact(contact));
contactRepository.save(contact);

Categories