I am working on an authentication model that would suit GlassFish's JDBC Realm requirements.
In this model I have one group which can contain multiple users, but each user can only be in one group (e.g. su, admin, etc.).
I have two entities: Groups.java (for groups) and Credential.java (for users) and intend to feed the generated join table to Glassfish's "Group Table" property.
I am able to persist both Groups and Credential instances, but the required middle table (credential_groups) is not even created, let alone updated.
Below are my entities:
Credential.java:
#Entity
#Access(AccessType.PROPERTY)
#Table(name = "credential")
public class Credential extends MetaInfo implements Serializable {
private String username;
private String passwd;
private Groups group;
private boolean blocked;
private boolean suspended;
public Credential() {
super();
}
public Credential(String createdBy) {
super(Instant.now(), createdBy);
}
public Credential(String createdBy, String username, String passwd) {
this(createdBy);
this.username = username;
this.passwd = passwd;
}
#Id
#Column(name = "username")
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
updateModified();
}
#Column(name = "passwd")
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
updateModified();
}
#ManyToOne(fetch = FetchType.LAZY)
public Groups getGroups() {
return group;
}
public void setGroups(Groups group) {
this.group = group;
group.getCredentials().add(this);
updateModified();
}
#Column(name = "is_blocked")
public boolean isBlocked() {
return blocked;
}
public void setBlocked(boolean blocked) {
this.blocked = blocked;
updateModified();
}
#Column(name = "is_suspended")
public boolean isSuspended() {
return suspended;
}
public void setSuspended(boolean suspended) {
this.suspended = suspended;
updateModified();
}
}
Groups.java:
#Entity
#Access(AccessType.PROPERTY)
#Table(name = "groups")
#NamedQueries({
#NamedQuery(name = "findAllGroups",
query = "SELECT g FROM Groups g order by g.modifiedDate DESC")
})
public class Groups extends MetaInfo implements Serializable {
private String groupName;
private Set<Credential> credentials;
public Groups() {
super();
credentials = new HashSet();
}
public Groups(String groupName) {
this();
this.groupName = groupName;
}
public Groups(String createdBy, String groupName) {
this();
setCreatedBy(createdBy);
this.groupName = groupName;
}
#Id
#Column(name = "group_name")
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
updateModified();
}
#OneToMany(mappedBy = "groups")
#JoinTable(
name = "credential_groups",
joinColumns = #JoinColumn(name = "group_name"),
inverseJoinColumns = #JoinColumn(name = "username")
)
public Set<Credential> getCredentials() {
return credentials;
}
public void setCredentials(Set<Credential> credentials) {
this.credentials = credentials;
}
public void addCredential(Credential c) {
credentials.add(c);
if (c.getGroups() != this) {
c.setGroups(this);
}
}
}
As I said persisting both works (for Groups I have also tried updating, querying), but this error keeps on firing on every operation with either entity:
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Can't write; duplicate key in table '#sql-4d2_54'
Error Code: 1022
Call: ALTER TABLE credential ADD CONSTRAINT FK_credential_GROUPS_group_name FOREIGN KEY (GROUPS_group_name) REFERENCES groups (group_name)
Query: DataModifyQuery(sql="ALTER TABLE credential ADD CONSTRAINT FK_credential_GROUPS_group_name FOREIGN KEY (GROUPS_group_name) REFERENCES groups (group_name)")
Important update:
This is the error that is being thrown even before the pasted above exception:
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'group VARCHAR(255) NOT NULL, PRIMARY KEY (credential, group))' at line 1
Error Code: 1064
Call: CREATE TABLE credential_groups (credential VARCHAR(255) NOT NULL, group VARCHAR(255) NOT NULL, PRIMARY KEY (credential, group))
As requested by Francois Motard, here's my jUnit method that triggers the error (the group is created in another test class):
public class CredentialRepositoryTests {
private final OCGroupsRepository groupRepo;
private final OCCredentialRepository credentialRepo;
public CredentialRepositoryTests() {
groupRepo = new OCGroupsRepository();
credentialRepo = new OCCredentialRepository();
}
...
#Test
public void createCredentialTest(){
//retrieve the group
Groups admin = groupRepo.getByGroupName("admin");
Credential rimma = new Credential("user_creator", "sample_user", "abc123");
admin.addCredential(rimma);
assertTrue(groupRepo.updateGroups(admin));
}
I based my code on the instructions from the EJB 3.0 in Action by Manning and tried to resolve the join table issue based on this stackoverflow answer: How to create join table with JPA annotations?
Can anyone help me have the join table created and updated? Thank you very much.
Resolved by removing the mappedBy attribute from the #OneToMany annotation of the owned entity (this fixed the non-creation of the join table) and by adding the unique attribute (set to true - this solved the "PRIMARY KEY (credential, group)" issue) to the #JoinColumn annotation inside the #JoinTable of the same property:
#OneToMany
#JoinTable(
name = "credential_groups",
joinColumns = #JoinColumn(name = "group_name"),
inverseJoinColumns = #JoinColumn(name = "username", unique=true)
)
public Set<Credential> getCredentials() {
return credentials;
}
Main take away don't ever ever combine a "mappedBy" with the #JoinTable in a ManyToOne/OneToMany entities pair.
Related
So I want the users to have many notes, and this means that the relation type between users and notes should be OneToMany(meaning one user has many notes). So i have a very strange bug in my application. When create and add the note to the database, and then i also save it in the users it works fine for the first time, however at second try i get the error "Cannot add or update a child row: a foreign key constraint fails". When i add one note to the database it works but when i add another note it gives the same error. I have fixed the bug with the set foreign_key_checks=0 in the database and it works, but it does not work when from my application.
Here are my codes for different classes:
Notes:
#Entity
#Table(name = "notes")
#Getter
#Setter
#ToString
public class Note {
#Id
#Column(name = "note_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String description;
#Temporal(TemporalType.TIMESTAMP)
private Date createdDate = new Date(System.currentTimeMillis());
}
Users:
#Entity
#Table(name = "users")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class User {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private String email;
private String nickname;
private Integer age;
#Column(nullable = false)
private String password;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "note_id", referencedColumnName = "user_id")
private Set<Note> notes = new HashSet<>();
public void addRole(Role role){
this.roles.add(role);
}
public void addNote(Note note){this.notes.add(note);}
public Set<Note> getNotes(){
return this.notes;
}
}
NoteService:
#Service
public class NoteService {
#Autowired
private NoteRepository noteRepository;
#Autowired
private UserService userService;
public List<Note> getAllNote(){
return noteRepository.findAll();
}
public Note getNote(Long id) throws noteNotFoundException {
Optional<Note> result = noteRepository.findById(id);
if(result.isPresent()){
return result.get();
}
//chveni sheqmnili exeptioni
throw new noteNotFoundException("Could not find any note with given ID: " + id);
}
public void save(Note note) {
noteRepository.save(note);
}
public void deleteNoteById(Long id) throws noteNotFoundException {
if(getNote(id)==null){
throw new noteNotFoundException("Could not find any note with given ID: " + id);
}
noteRepository.deleteById(id);
}
}
UserService:
#Service
public class UserService {
private final Integer USER_ROLE_ID = 1;
private final Long ADMIN_ID = 3L;
#Autowired
private UserRepository repository;
#Autowired
private RoleService roleService;
public List<User> getAllUser(){
return (List<User>) repository.findAll();
}
public List<User> getAllUsersWithoutAdmin(){
List<User> allUsers = repository.findAll();
User admin = repository.getById(ADMIN_ID);
allUsers.remove(admin);
return allUsers;
};
public void save(User u) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encodedPassword = encoder.encode(u.getPassword());
u.setPassword(encodedPassword);
try {
u.addRole(roleService.getRole(USER_ROLE_ID));
} catch (userNotFoundException e) {
e.printStackTrace();
}
repository.save(u);
}
public void saveNoteToUser(Note note){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = repository.findByEmail(authentication.getName());
user.addNote(note);
repository.save(user);
}
public Set<Note> getAllNotes(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = repository.findByEmail(authentication.getName());
return user.getNotes();
}
public User getUser(Long id) throws userNotFoundException {
Optional<User> result = repository.findById(id);
if(result.isPresent()){
return result.get();
}
//chveni sheqmnili exeptioni
throw new userNotFoundException("Could not find any user with given ID: " + id);
}
public void deleteUserById(Long id) throws userNotFoundException {
//eseigi jer itvli tu 0 ia an null errors abruen
//tu useri arsebobs mere adeleteb
Long count = repository.countById(id);
if(count == null || count==0){
throw new userNotFoundException("Could not find any user with given ID: " + id);
}
repository.deleteById(id);
}
}
And Finally Mapping:
#PostMapping("/notes/save")
public String saveNote(Note note, RedirectAttributes ra){
noteService.save(note);
userService.saveNoteToUser(note);
//ra atributi ari roca redirect moxdeba mere ro dawers messijs anu redirectis mere xdeba
ra.addFlashAttribute("message", "The note has been added successfully.");
return "redirect:/notes";
}
In mapping as you can see, firstly i am trying to save a note in the database, and after that i want to add that note to the user itself. However as mentioned above it works only once, however when i want to add another note i get this error:
java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`myfirstappdb`.`notes`, CONSTRAINT `FKb7tumg0c2p1wt2ifjag2gv998` FOREIGN KEY (`note_id`) REFERENCES `users` (`user_id`))
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117) ~[mysql-connector-java-8.0.27.jar:8.0.27]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.27.jar:8.0.27]
at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953) ~[mysql-connector-java-8.0.27.jar:8.0.27]
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1098) ~[mysql-connector-java-8.0.27.jar:8.0.27]
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1046) ~[mysql-connector-java-8.0.27.jar:8.0.27]
at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1371) ~[mysql-connector-java-8.0.27.jar:8.0.27]
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1031) ~[mysql-connector-java-8.0.27.jar:8.0.27]
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) ~[HikariCP-4.0.3.jar:na]
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) ~[HikariCP-4.0.3.jar:na]
According to the Hibernate Documentation when you have a UniDirectional relation in your entity schema and you have only the side of #OneToMany , you can't use the annotation #JoinColumn.
According to the Doc
When using a unidirectional #OneToMany association, Hibernate resorts
to using a link table between the two joining entities.
You must remove the #JoinColumn so that hibernate follows the default process of creating a intermediate join table and then it will be able to proceed.
SQL [n/a]; constraint [PRIMARY];
I created a CRUD RESTful API application using Java/Spring Boot. I have a MariaDB with a list of employees and relevant data inside. I am using MySQL Workbench as my SQL Client.
There are no errors when running the Spring Boot application. No errors in my database. No errors when I get the API of all Engineers in my database.
My "GET" requests work fine; however, "POST" does not. I am unable to create a new employee without having to directly insert into the database with MySQL WB.
I want to be able to create a new employee using APIs via Postman.
As you can see there is a ConstraintViolationException being thrown. The contraint is my Primary Key, which is "id." I've set that to auto-increment in MySQL WB.
Here is the code for my Engineer class:
package engineermanagement.model;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
#Entity
#Table(name = "Engineers")
#EntityListeners(AuditingEntityListener.class)
public class Engineer {
private Long id;
private String firstName;
private String lastName;
private String sid;
private String email;
private String manager;
private Boolean teamLead;
private String groupName;
private String shift;
private int startTime;
private int endTime;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "first_name", nullable = false)
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#Column(name = "last_name", nullable = false)
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#Column(name = "eid", nullable = false)
public String getEid() {
return eid;
}
public void setEid(String eid) {
this.sid = eid;
}
#Column(name = "email", nullable = false)
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#Column(name = "manager", nullable = false)
public String getManager() {
return manager;
}
public void setManager(String manager) {
this.manager = manager;
}
#Column(name = "teamlead", nullable = false)
public Boolean getTeamLead() {
return teamLead;
}
public void setTeamLead(Boolean teamLead) {
this.teamLead = teamLead;
}
#Column(name = "group_name", nullable = false)
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
#Column(name = "shift", nullable = false)
public String getShift() {
return shift;
}
public void setShift(String shift) {
this.shift = shift;
}
#Column(name = "start_time", nullable = false)
public int getStartTime() {
return startTime;
}
public void setStartTime(int startTime) {
this.startTime = startTime;
}
#Column(name = "end_time", nullable = false)
public int getEndTime() {
return endTime;
}
public void setEndTime(int endTime) {
this.endTime = endTime;
}
}
This is the code for my EngineerController class:
package jpmchase.controller;
import jpmchase.exception.*;
import jpmchase.model.Engineer;
import jpmchase.repository.EngineerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#RestController
#RequestMapping("/api/v1")
public class EngineerController {
#Autowired
private EngineerRepository engineerRepository;
#GetMapping("/engineers")
public List<Engineer> getAllEngineers() {
return engineerRepository.findAll();
}
#GetMapping("/engineers/{id}")
public ResponseEntity<Engineer> getEngineerById(
#PathVariable(value = "id") Long engineerId) throws ResourceNotFoundException {
Engineer engineer = engineerRepository.findById(engineerId)
.orElseThrow(() -> new ResourceNotFoundException("Engineer not found on :: "+ engineerId));
return ResponseEntity.ok().body(engineer);
}
#PostMapping("/engineers")
public Engineer createEngineer(#Valid #RequestBody Engineer engineer) {
return engineerRepository.save(engineer);
}
#PutMapping("/engineers/{id}")
public ResponseEntity<Engineer> updateEngineer(
#PathVariable(value = "id") Long engineerId,
#Valid #RequestBody Engineer engineerDetails) throws ResourceNotFoundException {
Engineer engineer = engineerRepository.findById(engineerId)
.orElseThrow(() -> new ResourceNotFoundException("Engineer not found on :: "+ engineerId));
engineer.setId(engineerDetails.getId());
engineer.setFirstName(engineerDetails.getFirstName());
engineer.setLastName(engineerDetails.getLastName());
engineer.setSid(engineerDetails.getSid());
engineer.setEmail(engineerDetails.getEmail());
engineer.setTeamLead(engineerDetails.getTeamLead());
engineer.setManager(engineerDetails.getManager());
engineer.setShift(engineerDetails.getShift());
engineer.setStartTime(engineerDetails.getStartTime());
engineer.setEndTime(engineerDetails.getEndTime());
// engineer.setUpdatedAt(new Date());
final Engineer updatedEngineer = engineerRepository.save(engineer);
return ResponseEntity.ok(updatedEngineer);
}
#DeleteMapping("/engineers/{id}")
public Map<String, Boolean> deleteEngineer(
#PathVariable(value = "id") Long engineerId) throws Exception {
Engineer engineer = engineerRepository.findById(engineerId)
.orElseThrow(() -> new ResourceNotFoundException("Engineer not found on :: "+ engineerId));
engineerRepository.delete(engineer);
Map<String, Boolean> response = new HashMap<>();
response.put("deleted", Boolean.TRUE);
return response;
}
}
Using Postman, I am trying to "POST" a new "test" employee but fails due to the aforementioned constraint. Any help will be appreciated...
Full stack trace:
2019-01-11 15:26:50.605 WARN 15276 --- [nio-8080-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1062, SQLState: 23000
2019-01-11 15:26:50.606 ERROR 15276 --- [nio-8080-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry '68' for key 'PRIMARY'
2019-01-11 15:26:50.606 ERROR 15276 --- [nio-8080-exec-6] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement]
2019-01-11 15:26:50.657 WARN 15276 --- [nio-8080-exec-6] .m.m.a.ExceptionHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [PRIMARY]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
I had this same issue and the reason is this
o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry '68' for key 'PRIMARY'.
Hibernate is trying to generate an Id for the new Engineer, but when it tries to follow the generation strategy specified by #GeneratedValue(strategy = GenerationType.AUTO) It gets an Id that already exists.
Most likely this happens because you manually added new rows to the Engineer table that collapses with the next generated Id values by Hibernate.
Also if you keep trying to insert a new Engineer multiple times you will end up succeeding since Hibernate increments the Id for the next insertion request until it finds an Id that doesn't already exist.
To solve the issue you can change the Id generation strategy to #GeneratedValue(strategy = GenerationType.IDENTITY) or keep trying to insert until Hibernate get to a non used Id value (If you want to keep the same generation strategy)
org.hibernate.exception.ConstraintViolationException,
The Exception constraint violation means you set database fields with non-null, unique, etc. The database checks the data before inserting it into the table. If any one of the conditions not matching you will get a ConstraintViolationException.
In your case you have a PRIMARY key violation. so check your database table Engineer with "id=68" exists or not. If it is there kindly remove the row.
I have a spring/jpa project that uses a mysql database. I have updated an already existent entity so that it has a new #OneToMany relationship with a new table that I have created (Which has an #ManyToOne annoation). I have gotten this to work with an embedded database with unit tests and is working properly. But when I try and deploy it to my development server, it seems that the mapping is not registered, and the data just returns a list with size 0 when it should return more.
The function that I call is
public List<Recommendation> getRecommendationsByEmployeeId()
I belive that this issue is related to some JPA magic that is done when creating a database, that does not occur when deployed against an already existent one. Is this right to assume? Or Why would this work when running my tests against an embedded database but not against the already existent development database?
These are the relevant entities.
#Entity
#Table(name = "recommendation")
public class Recommendation extends BusinessEntity {
#ManyToOne
#JoinColumn(name="employeeid")
#JsonBackReference
//This is simply to avoid a stackoverflow error
private Employee employee;
//New relationship
#OneToMany(mappedBy = "recommendation", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonBackReference
List<RecommendationImage> recommendationimages;
public Recommendation(){}
public Recommendation(Employee employee, String title, String description, double targetLat, double targetLong,
String street, String city, String country
) {
this.employee = employee;
this.title = title;
this.description = description;
this.targetLat = targetLat;
this.targetLong = targetLong;
this.street = street;
this.city = city;
this.country = country;
this.active = true;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public List<RecommendationImage> getRecommendationimages() {
return recommendationimages;
}
public void setRecommendationimages(List<RecommendationImage> recommendationimages) {
this.recommendationimages = recommendationimages;
}
}
New entity
#Entity
#Table(name = "recommendationimage")
public class RecommendationImage extends ImageEntity{
#ManyToOne
#JoinColumn(name="recommendationid")
private Recommendation recommendation;
public RecommendationImage(){}
public RecommendationImage(Recommendation recommendation, String path){
this.recommendation = recommendation;
this.path = path;
}
public Recommendation getRecommendation() {
return recommendation;
}
public void setRecommendation(Recommendation recommendation) {
this.recommendation = recommendation;
}
}
Any help with this issue would be great! Thanks!
I'm strugling with JPA. I tried several things but I can't figure out the right way to put the annotations.
What is want is like an Order/OrderLine relationship.
Thus:
Order( PK=orderId, fields=[...])
OrderLine (Pk1=orderId,Pk2=orderLineId, fields=[...])
Obviously, OrderLine.orderId refers to the 'Order' table.
What I functionally want to do is at least:
retrieve the Order with and without all orderlines. It should have a Set
retrieve an orderline by full PK, but without the associated Order.
retrieve a list of orderlines by orderId.
I only want these 2 tables and classes. nothing more nothing less.
I tried several things. Can anybody help me out with putting in the right annotations and members on these two classes?
Edit: what i've done so far.
Note that in this real example User=Order and UserRun=OrderLine. So, i am not interested in a seperate 'Run'-entity. Merely a UserRun as described by the Orderline.
#Entity
#Table(name = "user_runs")
public class UserRun {
#EmbeddedId
private UserRunKey id;
public UserRun(){};
public UserRun(String userName, String runUuid) {
this.id = new UserRunKey(userName, runUuid);
}
public String getUserName() {
return this.id.getUserName();
}
public String getRunUuid() {
return this.id.getRunUuid();
}
}
#Embeddable
class UserRunKey implements Serializable {
#Column(name = "username")
private String userName;
#Column(name = "run_uuid")
private String runUuid;
public UserRunKey(){};
public UserRunKey(String userName, String runUuid) {
this.runUuid = runUuid;
this.userName = userName;
}
public String getUserName() {
return userName;
}
public String getRunUuid() {
return runUuid;
}
}
This created a userruns/orderline table with the PK in the wrong way:
create table user_runs (run_uuid varchar(255) not null, username varchar(255) not null, primary key (run_uuid, username))
I want the primary key in reverse.
I want username as FK to User
I want a Set in my User-class.
When I do the following in my User-class:
#OneToMany
private Set<UserRun> userRuns;
It will create a
create table user_user_runs (user_username varchar(255) not null, user_runs_run_uuid varchar(255) not null, user_runs_username varchar(255) not null, primary key (user_username, user_runs_run_uuid, user_runs_username))
And that's something I definitely don't want! Once again, I don't want a Run-object (same as nobody's interested in a Line-class, from OrderLine)
I think I figured it out.
The UserRun/Orderline class:
#Entity
#Table(name = "user_runs")
public class UserRun {
#EmbeddedId
private UserRunKey id;
public UserRun(){};
public UserRun(String userName, String runUuid) {
this.id = new UserRunKey(userName, runUuid);
}
public String getUserName() {
return this.id.getUserName();
}
public String getRunUuid() {
return this.id.getRunUuid();
}
}
#Embeddable
class UserRunKey implements Serializable {
#Column(name = "username")
private String userName;
#Column(name = "run_uuid")
private String zrunUuid; //starts with a z, so the PK will be pk(username,run_uuid). Apparently, order in PK is determined from the variable names (alphabetic order)....
public UserRunKey(){};
public UserRunKey(String userName, String zrunUuid) {
this.zrunUuid = zrunUuid;
this.userName = userName;
}
public String getUserName() {
return userName;
}
public String getRunUuid() {
return zrunUuid;
}
}
In the userclass:
#OneToMany(mappedBy = "id.userName", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<UserRun> userRuns;
Unfortunately, there are 2 downsides:
I see that there are 2 queries executed instead of a Join on username. One to retrieve user, and 1 to retrieve the Set...
I needed to alter variablenames of the PK (compound/Embeddable). It seems there is no clean way to define the PK order. (Seriously?). Fortunately, the variable name is private, and not exposed by getter.
If anybody knows a cleaner way for these 2 issues. Let me know!
I think what you have to do is the following:
Because the primary key is compound key you need an ID class, as you already did:
#Embeddable
class OrderLinePK implements Serializable {
// you can use physical mapping annotations such as #Column here
#Column(name="...")
private Integer orderLineID;
// This is foreign key and the physical mapping should be done
// on the entity, and not here
private Integer orderID;
public OrderLinePK(){}
// getters + setters
// orverride equals() and hashCode() methods
}
Implement OrderLine entity
#Entity
public class OrderLine {
#EmbededId private OrderLinePK id;
#Mapsid("orderID")
#ManyToOne
#JoinColumn(name = "ORDER_ID", referencedColumn="ID")
private Order order;
// getters + setters ....
}
And the Order entity:
#Entity
public class Order {
#Id
private Integer id;
#OneToMany(fetch = FetchType.LAZY) // actually default by 1-to-n
private Coolection<OrderLine> orderLines;
// getters + setters ....
}
I have two classes Role and Privilege with the relation ManyToMany. When adding a Privilege to a Role, and then calling saveOrUpdate(role), I get the exception below.
Here is the Role class:
#Entity
#Table(name = "ROLES")
public class Role implements GenericDomain {
private static final long serialVersionUID = -7620550658984151796L;
private Long id;
private String code;
private String name;
private Set<User> users = new HashSet<User>(0);
private Set<Privilege> privileges = new HashSet<Privilege>(0);
public Role() {
}
public Role(String code, String name) {
this.code = code;
this.name = name;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "ID")
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#Column(name = "CODE", unique = true, nullable = false, length = 16)
#NotEmpty(message= "password.required")
#Length(min = 3, max = 16)
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
#Column(name="NAME", nullable = false, length = 64)
#NotEmpty
#Length(min = 1, max = 32)
public String getName() { return name; }
public void setName(String name) { this.name = name; }
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "ROLES_PRIVILEGES"
, joinColumns = { #JoinColumn(name = "ROLE_ID") }
, inverseJoinColumns = { #JoinColumn(name = "PRIVILEGE_ID") }
)
public Set<Privilege> getPrivileges() {
return this.privileges;
}
public void setPrivileges(Set<Privilege> privileges) {
this.privileges = privileges;
}
/* overide of hascode, equals*/
}
Here is the Privilege class:
#Entity
#Table(name = "PRIVILEGES")
public class Privilege implements GenericDomain {
private static final long serialVersionUID = 4649689934972816194L;
private Long id;
private String code;
private Set<Role> roles = new HashSet<Role>(0);
public Privilege() {
}
public Privilege(String code) {
this.code = code;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "ID")
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#Column(name = "CODE", unique = true, nullable = false, length = 16)
#NotEmpty(message= "password.required")
#Length(min = 3, max = 16)
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
#ManyToMany(cascade=CascadeType.REFRESH, mappedBy="privileges")
public Set<Role> getRoles() {
return this.roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
/*overide equals and hascode*/
}
And here is the the exception:
IllegalArgumentException in class: com.stunaz.domain.Privilege, getter method of property: id
....
javax.persistence.PersistenceException: org.hibernate.PropertyAccessException: IllegalArgumentException occurred calling getter of com.stunaz.domain.Privilege.id
....
java.lang.IllegalArgumentException: object is not an instance of declaring class
It seems something is wrong with my mapping, that somewhere I should pass an object but I am passing an Id.
Hibernate is not much user friendly when it comes to telling user what's wrong with mapping.
Solution:
Debug the app
Set a breakpoint for the event of IllegalArgumentException being thrown (anywhere)
Perform the operation which causes this
Go up through the call stack and inspect the active stack variables.
Using this procedure, I figured out what was wrong with my mapping.
In general, from what I have seen, it's usually wrong class explicitely stated somewhere, like #MapKeyClass(Wrong.class) when the key is actually String etc.
Or, calling wrong (Hibernate) API, like setParameter() instead of setParameterList():
Query query = session.getNamedQuery(queryName);
// org.hibernate.PropertyAccessException: IllegalArgumentException occurred calling getter
query.setParameter("children", aCollectionOfChildren);
query.list();
Or, in case of Criteria API, I have seen this:
DetachedCriteria crit = DetachedCriteria.forClass( Group.class );
crit.add( Restrictions.eq( "parent", id ) );
The getParent() method of Group returns a Group but I was attempting
to compare it against a Long.
To get an illegal argument exception calling the getId() method, it seems that Hibernate thinks the type of your id is something other than Long (probably Integer). Maybe try adding #Type(type="long") to your ids.
Whenever I have weird issues with Hibernate, I always attach the source code and debug around where the error happens. This can give you some insight into what Hibernate is trying to do, and help figure out where you may have missed something, or passed a bad argument somewhere.
At first glance your code seems fine except maybe for:
#ManyToMany(cascade=CascadeType.REFRESH, mappedBy="privileges")
I'm not sure if this is incorrect but this is the way I do it:
#ManyToMany(cascade=CascadeType.REFRESH, mappedBy="privileges", targetEntity = Roles.class)
This mappedBy property could even be omitted...
How are you saving/updating this?
Are you getting the 'Role' object for which you want to save the 'Permission' by calling findById(Long roleId)? Once you get that role object create a new Permission object and setRole(role) and set other properties and callthe saveOrUpdate(permission)? That should work.
Just a note for others although this might be not related to this problem. It happened to me that I was mapping a DTO coming from a REST API to an Entity. One of the child DTOs was not mapped correctly to a child Entity (I was using Dozer). Hibernate failed because the id of the DTO was not compatible with the id of the Entity.