Unique argument of column annotation is not considered - spring

In the entity attribute I make it to be unique :
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(unique=true)
private String role_code;
...
}
But at runtime when inserting a new record then no exception is raised , nor console ! Although database is correctly set as the role_code column is a varchar2 primary key :
#RequestMapping(value = "/save", method = RequestMethod.POST)
public ModelAndView save(#ModelAttribute("role_details") Role role) {
roleDao.saveOrUpdate(role);
return new ModelAndView("redirect:/adminrole/");
}
So how to enforce unique constraint at the entity level ?

Try:
#Entity
#Table(name = "role", indexes = {#Index(name = "some_index_name", columnList = "role_code", unique = true})
public class Role { ... }

Related

Representing Parent Child Relationship in Spring JPA

I have the following scenario. I have a parent class User.java which represents user table. I have two child classes - Doctor.java and Patient.java. The data for these classes are in 'user' table. The mapping between patient and doctor is provided in doctorpatient table. And this mapping can be ManyToMany. This 'doctorpatient' table is represented by DoctorPatient.java class
I need to map my doctor and patient class in such a way that my doctor class should return all patient for a particular doctor and patient class should return all doctors for a particular patient.
I am new to hibernate and would appreciate any help.
User.java
#Entity
#Table(name = "User")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
#Column(name="UserName", nullable = false, length = 16)
private String userName;
#Column(name="FirstName", nullable = false, length = 45)
private String firstName;
#Column(name="LastName", nullable = false, length = 45)
private String lastName;
#Column(name="Email", nullable = false, length = 45)
private String email;
// Getters and Setters
}
Doctor.java
public class Doctor extends User {
#OneToMany(mappedBy="doctor")
List<DoctorPatient> patientList;
//Getters and Setters
}
Patient.java
public class Patient extends User {
#OneToMany(mappedBy="patient")
List<DoctorPatient> doctorsList;
//Getters and Setters
}
DoctorPatient.java
#Entity
#Table(DoctorPatient)
public class DoctorPatient {
#EmbeddedId
private DoctorPatientId doctorPatientId;
#ManyToOne(targetEntity = Doctor.class)
#JoinColumn(name="doctorId")
private Doctor doctor;
#ManyToOne(targetEntity = Patient.class)
#JoinColumn(name="patientId")
private Patient patient;
//Getter Setters
}
Create command for DoctorPatient table
CREATE TABLE 'DoctorPatient' (
'DoctorId''INT(11) NOT NULL,
'PatientId' INT(11) NOT NULL,
PRIMARY KEY ('DoctorId', 'PatientId'),
INDEX 'FK_Doctor_idx' ('DoctorId'),
INDEX 'FK_Patient_idx' ('PatientId'),
CONSTRAINT 'FK_Patient' FOREIGN KEY ('PatientId') REFERENCES 'User' ('UserId'),
CONSTRAINT 'FK_Doctor' FOREIGN KEY ('DoctorId') REFERENCES 'User' ('UserId')
)
Currently for each doctor, I am getting the patientid (userId) from the DoctorPatient table using which I need to query the user table to get the patient details.
Similarly for patients, I am able to retrieve only doctorid (UserId) from DoctorPatient table. Then I need to iterate through the list and hit the User table to get complete details.
Solution
Updated classes
Patient.java
public class Patient extends User {
#ManyToMany
#JoinTable(name="DoctorPatient", joinColumns= {#JoinColumn(name="PatientId") }, inverseJoinColumns={ #JoinColumn(name="DoctorId") } )
private Set<Doctor> DoctorList = new HashSet<>();
//Getters and Setters
}
Doctor.java
public class Doctor extends User {
#ManyToMany(mappedBy="DoctorList")
private Set<Patient> patients = new HashSet<>();
//Getters and Setters
}

Get LiveData from a many-to-many structure in android Room Architecture Component

Let's take a basic example
A table to store the users
#Entity (tableName="users")
class UsersEntity(
#PrimaryKey val id
var name:String,
...
)
A table to store the roles
#Entity (tableName="roles")
class RolesEntity(
#PrimaryKey val id
var name:String,
...
)
A table to store the many to many relation between users and roles
#Entity (tableName="roles")
class UserRoles(
#PrimaryKey val id
var userId:String,
var roleId:String
)
The pojo class I need in my View
class user(
var id:String,
var name:String,
.... other fields
var roles: List<Role>
)
In my ViewModel how can I pass a user result as LiveData having also the List<Role> filled?
Looking at a general way, I could:
have UserDao.getUserById(id) which returns LiveData from the users table and RoleDao.getRolesForUserId(id) which returns LiveData with a list of roles for a user. Then in my fragment, I can do viewModel.getUserById().observe{} and viewModel.getRolesForUserId().observe{}. But this basically means having 2 observers and I'm pretty confident that it's not the way to go.
probably other way would be to be able to mix them somehow in my repository or viewmodel so it returns what I need. I'll look into MediatorLiveData
Create a different model with the User and its Roles, and use #Embedded and #Relation annotations.
Take for example:
public class UserModel {
#Embedded
UserEntity user;
#Relation(parentColumn = "id", entityColumn = "userId", entity = UserRoles.class)
List<UserRoleModel> userRoles;
}
public class UserRoleModel {
#Embedded
UserRoles userRole;
#Relation(parentColumn = "roleId", entityColumn = "id")
List<RoleEntity> roles; // Only 1 item, but Room wants it to be a list.
}
You can use the UserModel from here.
It's ok to have multiple different data flow in one screen.
On the one hand we can talk about changing user roles list without changing user itself on the other hand user name can be changed without updating roles list. Addition benefit of using multiple data flow, you can show user data while loading user roles.
I suppose, you have pojo of user and roles to avoid synchronization issues. You can implement smooth data delivering (from db to view) like in sample below:
View model
public class UserRolesViewModel extends ViewModel {
private final MutableLiveData<Integer> mSelectedUser;
private final LiveData<UsersEntity> mUserData;
private final LiveData<List<RolesEntity>> mRolesData;
private DataBase mDatabase;
public UserRolesViewModel() {
mSelectedUser = new MutableLiveData<>();
// create data flow for user and roles synchronized by mSelectedUser
mUserData = Transformations.switchMap(mSelectedUser, mDatabase.getUserDao()::getUser);
mRolesData = Transformations.switchMap(mSelectedUser, mDatabase.getRolesDao()::getUserRoles);
}
public void setDatabase(DataBase dataBase) {
mDatabase = dataBase;
}
#MainThread
public void setSelectedUser(Integer userId) {
if (mDatabase == null)
throw new IllegalStateException("You need setup database before select user");
mSelectedUser.setValue(userId);
}
public LiveData<UsersEntity> getUserData() {
return mUserData;
}
public LiveData<List<RolesEntity>> getRolesData() {
return mRolesData;
}
}
It's better to encapsulate data source implementation in Repository class and inject it via DI like in this paragraph.
Database sample based on Many-to-Many paragraph from this article
Entities
Users
#Entity(tableName = "users")
public class UsersEntity {
#PrimaryKey
public int id;
public String name;
}
Roles
#Entity(tableName = "roles")
public class RolesEntity {
#PrimaryKey
public int id;
public String name;
}
User roles
This entity require special attention because we need to declare foreign keys to make joun operations futire
#Entity(tableName = "user_roles", primaryKeys = {"user_id", "role_id"}, foreignKeys = {
#ForeignKey(entity = UsersEntity.class, parentColumns = "id", childColumns = "user_id"),
#ForeignKey(entity = RolesEntity.class, parentColumns = "id", childColumns = "role_id")
})
public class UserRolesEntity {
#ColumnInfo(name = "user_id")
public int userId;
#ColumnInfo(name = "role_id")
public int roleId;
}
Dao
Users dao
#Dao
public interface UserDao {
#Query("SELECT * from users WHERE id = :userId")
LiveData<UsersEntity> getUser(int userId);
}
Roles dao
#Dao
public interface RolesDao {
#Query("SELECT * FROM roles INNER JOIN user_roles ON roles.id=user_roles.role_id WHERE user_roles.user_id = :userId")
LiveData<List<RolesEntity>> getUserRoles(int userId);
}
Data base
#Database(entities = {UsersEntity.class, RolesEntity.class, UserRolesEntity.class}, version = 1)
public abstract class DataBase extends RoomDatabase {
public abstract UserDao getUserDao();
public abstract RolesDao getRolesDao();
}

Eager fetch property in Mapped Super class

Our Mapped Super class has createdBy column which is defined to be lazily loaded
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class AbstractAuditingEntity implements Serializable {
#CreatedBy
#ManyToOne(fetch = FetchType.LAZY)
#XmlTransient
#JoinColumn(name = "created_by", updatable = false, columnDefinition = "bigint")
protected User createdBy;
public User getCreatedBy() {
return createdBy;
}
public void setCreatedBy(User createdBy) {
this.createdBy = createdBy;
}
I need to load this property eagerly in one of the sub class that inherits this aforementioned class.
#Override
#XmlElement(name = "createdBy")
#JsonProperty("createdBy")
public User getCreatedBy() {
return super.getCreatedBy();
}
How can I do that?
I tried the following (used NamedEntityGraph and HQL), but, both did not return createdBy from MappedSuperClass that is defined as lazy
//defined at the top of Model
#NamedEntityGraphs({
// eagerly fetches created by and program names when used
#NamedEntityGraph(
name = "graphWithCreatedBy",
attributeNodes = {
#NamedAttributeNode("createdBy")
}
)
})
//Repository method
#EntityGraph(value = "Program.createdBy", type = EntityGraph.EntityGraphType.FETCH) //tried both LOAD and FETCH
Program findOne(Specification<Program> specification);
---Using HQL FETCH JOIN --
//Repository Implementation
private static final String PROGRAM_USER_QUERY = "SELECT " +
" sp FROM Program sp " +
" LEFT JOIN FETCH sp.createdBy where sp.id = :id";
Query query = entityManager.createQuery(PROGRAM_USER_QUERY ).
setParameter("id", id);
query.getSingleResult();
Both approaches returns program, but not the createdBy User
What am I doing wrong?

Querying composite table in Hibernate

I am working on a Spring-MVC application where I have a many-to-many relationship in which I have to query in 2 tables to get the values I require. I will explain in more detail.
I have 2 tables GroupAccount, GroupMembers with many-to-many
relationship. Now there is a junction table called membertable where
id from GroupMembers and GroupAccount is stored.
This is what I am looking for :
I pass a groupAccounId and username as parameters. Now, in the
GroupMembers table, there is a username stored. In groupAccount,
there is groupAccountId is stored.
Now in the memberjunction, I have composite key
memberid,GroupAccountId, I would like the member id for the username
which has a matching groupAccountId I submit.
Below is the SQL code and Spring-mvc code to understand more better.
CREATE TABLE public.groupaccount (
groupid NUMERIC NOT NULL,
groupname VARCHAR,
groupaccountstatus BOOLEAN DEFAULT false NOT NULL,
adminusername VARCHAR,
CONSTRAINT groupid PRIMARY KEY (groupid)
);
CREATE TABLE public.groupmembers (
memberid INTEGER NOT NULL,
musername VARCHAR
CONSTRAINT memberid PRIMARY KEY (memberid)
);
CREATE TABLE public.memberjunction (
memberid INTEGER NOT NULL,
groupid NUMERIC NOT NULL,
CONSTRAINT membergroupid PRIMARY KEY (memberid, groupid)
);
GroupMembersDAOImpl :#
#Override
public List<Integer> returnMemberIdWithMatchingUsername(String memberUsername) {
session = this.sessionFactory.getCurrentSession();
org.hibernate.Query query = session.createQuery("From GroupMembers as " +
"n where n.memberUsername=:memberUsername");
query.setParameter("memberUsername",memberUsername);
List<GroupMembers> memberList = query.list();
List<Integer> memberIdList = new ArrayList<>();
for(GroupMembers members :memberList){
memberIdList.add(members.getMemberid());
}
return memberIdList;
}
GroupAccount model :
#Entity
#Table(name="groupaccount")
public class GroupAccount {
#Id
#Column(name="groupid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "groupaccount_seq_gen")
#SequenceGenerator(name = "groupaccount_seq_gen",sequenceName = "groupaccount_seq")
private Long groupId;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "memberjunction", joinColumns = {#JoinColumn(name = "groupid")},
inverseJoinColumns = {#JoinColumn(name = "memberid")})
private Set<GroupMembers> groupMembersSet = new HashSet<>();
public void setGroupMembersSet(Set<GroupMembers> groupMembersSet){
this.groupMembersSet = groupMembersSet;
}
}
GroupMembers model class :
#Entity
#Table(name="groupmembers")
public class GroupMembers {
#Id
#Column(name="memberid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "groupmembers_seq_gen")
#SequenceGenerator(name = "groupmembers_seq_gen",sequenceName = "groupmembers_seq")
private int memberid;
#ManyToMany(mappedBy = "groupMembersSet")
private Set<GroupAccount> groupAccounts = new HashSet<>();
public void setGroupAccounts(Set<GroupAccount> groupAccounts){
this.groupAccounts = groupAccounts;
}
public Set<GroupAccount> getGroupAccounts(){
return this.groupAccounts;
}
}
Query I am using :
#Override
public int getMemberIdForCanvas(String memberUsername, Long groupId) {
session = this.sessionFactory.getCurrentSession();
org.hibernate.Query query = session.createQuery("select distinct m.memberId from GroupMembers m\n" +
"join m.groupAccounts a\n" +
"where a.memberUsername = :userName and m.groupId=:groupId");
query.setParameter(memberUsername,"memberUsername");
query.setParameter(String.valueOf(groupId),"groupId");
int memberid = (Integer)query.uniqueResult();
return memberid;
}
Any help would be nice. Thanks a lot.
Here's the documentation for joins and HQL. Please read it.
The query is as simple as
select distinct m.memberId from GroupMembers m
join m.groupAccounts a
where a.memberUsername = :userName
Please also fix your naming. A GroupMembers instance is a single group member. So the class should be named GroupMember, without s. Repeating the name of the class in the fields of this class is also redundant: member.getId() is more readable and less verbose than member.getMemberId(). Same for the other fields.

Hibernate: ManyToMany inverse Delete

I have a ManyToMany relationship between two tables, User and Keyword. The User is the owner of the relationship. If I delete a User, I remove first all Keywords from this User and then delete the User. This works as expected.
But I don't know how to delete a Keyword and automatically delete the relations to all Users.
Here is my code so far.
#Entity
#Table(name = "user")
public class User {
#Id
#Column(name = "id")
#GeneratedValue
private Integer id;
#Column(name = "name")
private String name;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
#JoinTable(name = "user_has_keyword", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "keyword_id"))
private List keywords = new ArrayList();
// Getters and setters
...
}
#Entity
#Table(name = "keyword")
public class Keyword {
#Id
#Column(name = "id")
#GeneratedValue
private Integer id;
#Column(name = "keyword")
private String keyword;
#ManyToMany(mappedBy = "keywords")
private List users = new ArrayList();
// Getters and setters
...
}
#Service("myService")
#Transactional("transactionManager")
public class MyService {
#Resource(name = "sessionFactory")
private SessionFactory sessionFactory;
#SuppressWarnings("unchecked")
public List getAllUsers() {
Session session = this.sessionFactory.getCurrentSession();
Query query = session.createQuery("FROM User");
return query.list();
}
public User getUser(Integer id) {
Session session = this.sessionFactory.getCurrentSession();
return (User) session.get(User.class, id);
}
public void addUser(User user) {
Session session = this.sessionFactory.getCurrentSession();
session.save(user);
}
public void deleteUser(User user) {
Session session = this.sessionFactory.getCurrentSession();
// 1st, delete relations
user.getKeywords().clear();
session.update(user);
// 2nd, delete User object
session.delete(user);
}
public Keyword getKeyword(Integer id) {
Session session = this.sessionFactory.getCurrentSession();
return (Keyword) session.get(Keyword.class, id);
}
public Keyword addKeyword(Keyword keyword) {
Session session = this.sessionFactory.getCurrentSession();
session.save(keyword);
return keyword;
}
public void deleteKeyword(Keyword keyword) {
Session session = this.sessionFactory.getCurrentSession();
// 1st, delete relations
keyword.getUsers().clear();
session.update(keyword);
// 2nd, delete User object
keyword = getKeyword(keyword.getId());
session.delete(keyword);
}
}
#Controller
public class MyController {
#Resource(name = "myService")
private MyService myService;
#RequestMapping(value = "/add", method = RequestMethod.GET)
public String add(Model model) {
Keyword k = new Keyword();
k.setKeyword("yellow");
k = myService.addKeyword(k);
User u1 = new User();
u1.setName("Bart");
u1.getKeywords().add(k);
myService.addUser(u1);
User u2 = new User();
u2.setName("Lisa");
u2.getKeywords().add(k);
myService.addUser(u2);
return "/";
}
#RequestMapping(value = "/delete/user", method = RequestMethod.GET)
public String deleteUser(Model model) {
User u = myService.getUser(1);
myService.deleteUser(u);
return "/";
}
#RequestMapping(value = "/delete/keyword", method = RequestMethod.GET)
public String deleteKeyword(Model model) {
Keyword k = myService.getKeyword(1);
myService.deleteKeyword(k);
return "/";
}
}
If I browse to /delete/keyword I get the following exception:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.blabla.prototype.Keyword.users, no session or session was closed
I've googled and tried many different things, but nothing works.
I appreciate any help.
Thank you very much,
Marco
The LazyInitializationException has nothing to do with deletion. You're loading a Keyword in your controller. This makes the service load the keyword, without initializing its lazy list of users. This keyword is then returned to the controller, and the transaction is committed, and the session is closed, making the keyword detached from the session.
Then you pass this detached keyword to the service to delete it. The service thus receives a detached keyword, and tries to access its list of users. Since the keyword is detached and the list of users hasn't been loaded yet, this causes a LazyInitializationException.
The service method should take the ID of the keyword to delete as argument, load it, and thus work with an attached keyword, and then proceed with the deletion.
Now to answer your question, you got it right for the user deletion: you remove all the keywords from the user to delete, because the user is the owner of the association. Apply the same logic when deleting a keyword : remove the keyword from all the users referencing it, and delete the keyword:
public void deleteKeyword(Integer id) {
Keyword keyword = getKeyword(id);
for (User user : keyword.getUsers()) {
user.removeKeyword(keyword);
}
session.delete(keyword);
}
Note that you don't have to call update() when working with attached entities. The state of attached entities is automatically and transparently saved to the database.

Resources