Hibernate - OneToMany - Several Columns - java

I have those 2 tables Teacher and Contact, a teacher can have x Contacts. So here we are looking at a #OneToMany association.
Tables Structure:
User [userid, username, email,...]
Contact [contactid, contactname, ref, reftype,...]
I want to load from my User Class all the user's contacts. To do that I would do a query like
Select * from contact as c WHERE c.ref=8240 AND c.reftype='T';
8240 being a random userid and reftype T being for Teacher. As this contact table is used as well for school contacts and/or anyother type of customer we could have. The problem is I have no idea how to do this with Hibernate. Should I use embedbedId? Or a JoinColumns?
What I have done so far is to link my teacher to contacts having contact.ref=teacher.teacherid but what I want is :
contact.ref=teacher.teacherid AND contact.reftype='T'
How do I do that?
Here is my code
Teacher.class
private Integer teacherid;
private Set<Contact> contact;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "teacherid", unique = true, nullable = false)
public Integer getTeacherId() {
return teacherid;
}
#OneToMany(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name="ref"),
})
public Set<Contact> getContact() {
return contact;
}
public void setContact(Set<Contact> contact) {
this.contact = contact;
}
Contact.class
#Entity
#Table(name = "contact")
public class Contact implements java.io.Serializable {
private Integer contactid;
private String contactname;
private String contacttype;
private String reftype;
private int ref;
/*private Teacher teacher;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "ref"),
#JoinColumn(name = "reftype")
})
public Teacher getTeacher() {
return teacher;
}
public void setTeacher (Teacher teacher) {
this.teacher= teacher;
}
*/
private Set<ContactItem> contactItems;
private Set<ContactAddress> contactAddressess;
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="contactid")
public Set<ContactItem> getContactItems(){
return contactItems;
}
public void setContactItems(Set<ContactItem> contactItems) {
this.contactItems = contactItems;
}
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="contactid")
public Set<ContactAddress> getContactAddressess(){
return contactAddressess;
}
public void setContactAddressess(Set<ContactAddress> contactAddressess) {
this.contactAddressess = contactAddressess;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "contactid", unique = true, nullable = false)
public Integer getContactid() {
return this.contactid;
}
public void setContactid(Integer contactid) {
this.contactid = contactid;
}
#Column(name = "contactname", nullable = false)
public String getContactname() {
return this.contactname;
}
public void setContactname(String contactname) {
this.contactname = contactname;
}
#Column(name = "contacttype", nullable = false)
public String getContacttype() {
return this.contacttype;
}
public void setContacttype(String contacttype) {
this.contacttype = contacttype;
}
#Column(name = "reftype", nullable = false, length = 1)
public String getReftype() {
return this.reftype;
}
public void setReftype(String reftype) {
this.reftype = reftype;
}
#Column(name = "ref", nullable = false)
public int getRef() {
return this.ref;
}
public void setRef(int ref) {
this.ref = ref;
}
public String toString(){
return "\n#"+this.contactname+" : ("+this.ref+"-"+this.reftype+") \n"
+"#Items-----\n"+getContactItems()+"\n"
+"#Address---\n"+getContactAddressess()+"\n";
}
}

Assuming that Teacher is a User, and that every user has contacts.
User.class
#OneToMany(mappedBy = "user", targetEntity = Contact.class, orphanRemoval=true)
#Cascade(CascadeType.ALL)
private Set<Contact> contacts = new ConcurrentSkipListSet<Contact>();
//No setContacts here.
Contact.class
#ManyToOne
private User user;
public void setUser(User user){
this.user = user;
}
That's it.

First, since there's a User table and no Teacher table (teachers seem to be a sub-set of user rows, denoted by a 'type' column) I wouldn't have a table of User and a Teacher model. I would have only a User model instead. Hibernate is much easier if you do things the Hibernate way, which is one model per table with the model having the same name. For example, if you do this, you can use a tool to auto-generate (reverse engineer) all your model classes. This means a Hibernate tool will look at your tables, foreign keys, etc and generate appropiate Java code for your tables. Very very convenient when you start making table changes.
Normally you'll reverse engineer the model classes. Since these are machine-generated you don't want to change them because the changes will be over-written the next time to reverse-engineer the models. What I do for conditions such as yours is to create a class called a DAO - Data Access Object, or DAO.
public class UserDAO {
public static User getTeacher(EntityManager em, Long id) {
try {
IForgotTheType query = em.createQuery("User from User user, Contact contact where contact.ref=user.teacherid AND contact.reftype='T' and User.id=:id");
query.setParameter("id", id);
return (User) query.getSingleResult();
} catch (NoResultException e) {
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Obviously I am not sure about your table structure and column names but you get the idea. You can see where I inserted your code into the query above. Now you can get a teacher by simply calling UserDAO.getTeacher(). Use DAOs - otherwise you'll have Hibernate code everywhere in your code making maintenance more difficult.
Check out section 3.4 of this.

Related

#OneToMany not creating a column in table

I've really been trying to solve this but whatever I try it is not working. Throughout other tables it has always worked but with the table I am currently making whatever I do a column that I want creating is never being created. I want a seasons_team table with a list of teams but after finishing the relationship in JPA the column never appears. Is there something wrong I'm doing?
Here is how I am creating the table
#Entity
public class SeasonFixtures {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int SeasonFixtureID;
#OneToOne
private Season seasonId;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name="season")
private List<Team> teams;
public int getSeasonid() {
return seasonid;
}
public void setSeasonid(int seasonid) {
this.seasonid = seasonid;
}
public String getSeasonName() {
return seasonName;
}
public void setSeasonName(String seasonName) {
this.seasonName = seasonName;
}
public SeasonTeams getSfid() {
return sfid;
}
public void setSfid(SeasonTeams sfid) {
this.sfid = sfid;
}
And here is the other end of the relationship
#Entity
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int Team_Id;
private String teamName;
private Blob Logo;
private String stadium;
#OneToOne
private TotalTeamStats tts;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SEASON_ID", referencedColumnName = "seasonid", unique=false)
private Season season;
public String getStadium() {
return stadium;
}
public void setStadium(String stadium) {
this.stadium = stadium;
}
public TotalTeamStats getTts() {
return tts;
}
public void setTts(TotalTeamStats tts) {
this.tts = tts;
}
public int getTeam_Id() {
return Team_Id;
}
public void setTeam_Id(int team_Id) {
Team_Id = team_Id;
}
public String getTeamName() {
return teamName;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
public Blob getLogo() {
return Logo;
}
public void setLogo(Blob blobs) {
Logo = blobs;
}
But this is a screenshot of my table in MYSQL
image of MYSQL
If anyone could help I would be greatly appreciative. I've been trying to fix this for ages. But I don't know where else to turn.
There is something wrong with your Annotations or your definition of the one to many relation. I could not extract how your one to many relation is defined, but i think its should look like that:
#ManyToOne
#JoinColumn(name="season")
private Season season;
#OneToMany(fetch = FetchType.LAZY)
private List<Team> teams;
I'm not 100% sure but i think in a one to many relation the #JoinColumn have to be at #ManytoOne site.
UPDATE
Maybe because you are using seasonId as Reference. Either Map the relation with the Season Entity or you maybe need to add the Column:
#OneToOne
private Season seasonId;
to your Table with:
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "seasonId")
private Season season;
Again just a thought.

Unable to retrieve records using Spring Data JPA repository when foreign key is null

I am facing an issue when I am searching an Entity and its records with a foreign key. As per the business logic, I need to search Entity - LookupRequest table with account (with not null account id) and foreign key = "callback" (with null).
Please note that account id is not unique (for simplicity I deleted other parameters in the table to make it short). Once I filter the record, I have to update LookupRequest.callBack to foreignkey entry. I am unable to retrieve the records when foreign key is null.
import javax.persistence.*;
#Entity
public class LookupRequest {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
#Column
private String account;
#Column
private String caseId;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "id", unique=false, nullable=true, updatable=true)
private Callback callback;
public LookupRequest(String account, String caseId) {
this.account = account;
this.caseId = caseId;
callback = null;
}
public Long getId() {
return id;
}
public String getAccount() {
return account;
}
public String getCaseId() {
return caseId;
}
public Callback getCallback() {
return callback;
}
public void setCallback(Callback callback) {
this.callback = callback;
}
}
#Repository
public interface LookupRequestRepository extends JpaRepository<LookupRequest, Long> {
#Query("SELECT lr from LookupRequest lr where lr.account = :account and lr.callback = :callback")
LookupRequest findCaseId(#Param("account") String account, #Param("callback") String callback);
}
#Getter
#Setter
#Entity
public class Callback {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
public Long id;
#Column(length = 20000)
private String rawResponse;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void setRawResponse(String rawResponse) {
this.rawResponse = rawResponse;
}
}
then I am calling as follows:
LookupRequest lookupRequestFromDB = lookupRequestRepository.findCaseId("ABC", null);
What is going wrong?
NULL does not compare equal with anything, you need to use "is NULL".
See here
In your case:
SELECT lr from LookupRequest lr where lr.account = :account and (lr.callback = :callback or lr.callback is null)

How to find the reference when key to the find(Object.Class, {CompositeKey}) method is a composite key?

How to find a reference when we have composite key(two or more columns) to pass on as second parameter to the JPA entityManager.find(Object.class, compositeKey)?
My try-
I have created an Arraylist and added the values forming compositeKey it and then passing this list to the find method.
For ex: In my situation, userid and projectid together is the key for the UserProject table and these two have been added to the arraylist named as list, which will be passed as a second parameter to the entityManager find method as shown below:
List<Integer> list = new ArrayList<Integer>();
list.add(userProjectDO.getUserid());
list.add(userProjectDO.getProjectid());
UserProject userProject = em.find(UserProject.class,list);
But this always return as null, even though userid and projectId exists on the table. Has anyone been into similar issue? Solution?
JPA's EntityManager#find doesn't accept arrays as key but Object. Since you are talking about composite key you should implement your key in a separate class which will represent the composite key by listing all the key separate properties. You can achieve this using EmbeddedId for instance.
For example:
You should define the composite key class and annotate with #Embeddable:
public class UserProjectKey implements Serializable{
private String userId;
private String projectId;
//constructors, getters, setters
}
and use it as #EmbeddedId in your entity.
To search by the key you can do:
UserProjectKey key = new UserProjectKey("userIdExample", "projectIdExample");
em.find(UserProject.class, key);
I have found another approach i.e. writing namedQuery to search the table. Posting the implementation just in case it helps anyone.
final Query query = em.createNamedQuery("UserProject.findByAll");
UserProject Entity class:
#Entity
#Table(name = "userproject", schema = "public")
#NamedQueries({ #NamedQuery(name = "UserProject.findByAll", query = "SELECT a FROM UserProject a where a.userid = :userid and a.projectid = :projectid"),
#NamedQuery(name = "UserProject.findByUserId", query = "SELECT a FROM UserProject a where a.userid = :userid"),
#NamedQuery(name = "UserProject.findById", query = "SELECT a FROM UserProject a where a.id = :id" )})
public class UserProject implements Serializable {
private static final long serialVersionUID = 1L;
#Id #GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "userid")
private Integer userid;
#Column(name = "projectid")
private Integer projectid;
#Column(name = "created")
private Timestamp created;
#Column(name = "modified")
private Timestamp modified;
#Column(name = "modifiedbyid")
private Integer modifiedbyid;
#Column(name = "role")
private String role;
public Integer getId() {
return id;
}
public void setId(final Integer id) {
this.id = id;
}
public Integer getUserid() {
return userid;
}
public void setUserid(final Integer userid) {
this.userid = userid;
}
public void setProjectid(final Integer projectid) {
this.projectid = projectid;
}
public Timestamp getCreated() {
return created;
}
public void setCreated(final Timestamp created) {
this.created = created;
}
public Timestamp getModified() {
return modified;
}
public void setModified(final Timestamp modified) {
this.modified = modified;
}
public Integer getModifiedbyid() {
return modifiedbyid;
}
public void setModifiedbyid(final Integer modifiedbyid) {
this.modifiedbyid = modifiedbyid;
}
public String getRole() {
return role;
}
public void setRole(final String role) {
this.role = role;
}
}
And finally set the query parameters i.e. compositeKey values(userid,projectid) as :
final Query query = em.createNamedQuery("UserProject.findByAll");
query.setParameter("userid",userProjectDO.getUserid());
query.setParameter("projectid",userProjectDO.getProjectid());
List<UserProject> userProjectList = query.getResultList();
userProjectList would contain the row which matches the compositeKey (userId,projectId)
One advantage I see with this approach is that I can write N number of named queries inside the entity class as per the need/requirement. For ex: If we need to work on a view created out of this table. It can be easily achieved by first creating the view and then write another named query to work on it.

Hibernate- Avoid many-to-many association- How to implement it right?

I'm a newbie to JPA and Hibernate.
I was able to set up some small basic examples as expected.
Now I'm trying to use it for a first real world project.
Studying the Hibernate best practices I found that you should avoid many-to-many relationships. I found relating questions here and I do understand the concept why not to use it but I'm not understanding how it should be implemented.
So when I have the often used example of a user that can the part of many groups and a group that does have many users, how to implement that.
So this is a many-to-many relationship. But I should not use many-to-many, as far as I understood because it is very likely that I will need other information in the future, like a specific role a user has in a group or a date when he joined.
So I use a two one-to-many relationships and a joining table which doesnt only contains the ids but also will contain additional information like role or date.
Is that right?
And then neither the
class group has a property users
nor
the class users has a property groups
both have a property joinTableEntries?
Did I get the concept right so far?
#Entity
#Table(name="user")
public class User {
private int userId;
private String username;
private Set<JTUserGroup> jtUserGroupSet=new HashSet<JTUserGroup>(0);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userid")
public int getUserId()
{
return this.userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
public Set<JTUserGroup> getJtUserGroupSet() {
return jtUserGroupSet;
}
public void setJtUserGroupSet(Set<JTUserGroup> jtUserGroupSet) {
this.jtUserGroupSet = jtUserGroupSet;
}
}
#Entity
#Table(name = "forumgroup")
public class Group {
private int groupId;
private String groupname;
private Set<JTUserGroup> jtUserGroupSet=new HashSet<JTUserGroup>(0);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "groupid")
public int getGroupId() {
return groupId;
}
public void setGroupId(int groupId) {
this.groupId = groupId;
}
public String getGroupname() {
return groupname;
}
public void setGroupname(String groupname) {
this.groupname = groupname;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "group")
public Set<JTUserGroup> getJtUserGroupSet() {
return jtUserGroupSet;
}
public void setJtUserGroupSet(Set<JTUserGroup> jtUserGroupSet) {
this.jtUserGroupSet = jtUserGroupSet;
}
}
#Entity
#Table(name = "jtusergroup")
public class JTUserGroup {
private int joinId;
private User user;
private Group group;`enter code here`
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public int getJoinId() {
return joinId;
}
public void setJoinId(int joinId) {
this.joinId = joinId;
}
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "userid", nullable = false)
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "groupid", nullable = false)
public Group getGroup() {
return group;
}
public void setGroup(Group group) {
this.group = group;
}
}
and then to use these I add a new user to a group like this:
User user1=new User();
User user2=new User();
User user3=new User();
Group group1=new Group();
Group group2=new Group();
user1.setUsername("Mark");
user2.setUsername("Anton");
user3.setUsername("Maggy");
group1.setGroupname("Mark_Maggy");
group2.setGroupname("Maggy_Anton");
JTUserGroup jt1=new JTUserGroup();
jt1.setGroup(group1);
jt1.setUser(user1);
JTUserGroup jt2=new JTUserGroup();
jt2.setGroup(group1);
jt2.setUser(user3);
JTUserGroup jt3=new JTUserGroup();
jt3.setGroup(group2);
jt3.setUser(user3);
JTUserGroup jt4=new JTUserGroup();
jt4.setGroup(group2);
jt4.setUser(user2);
GenericDAO<JTUserGroup> jtDao=new GenericDAO<JTUserGroup>();
jtDao.beginTransaction();
jtDao.insert(jt1);
jtDao.insert(jt2);
jtDao.insert(jt3);
jtDao.insert(jt4);
jtDao.commit();
Just image this, you have User and let's say that Group is extending your user. Now your 'Group' has a sub-class which is JTUserGroup. Let's say that User has one-to-many relationship with Group (based on your logic, user can belong on many groups). Now the question, how can User know about JTUserGroup? You need somehow to get FK(as everybody knows that FK creates assosiations with classes) in your User class to know about all your 'Group' sub-classes and rely on logic, belong (let's say) for a several sub-classes which belongs to 'Group'. It is not impossible to do it or you need to make complex solutions to implement it, and what about SQL queries - it will look very complex. So you need to handle it somehow - the solution is inheritance. Doing this you can create associations between classes in very easy way. If you will not have many info (or let's say complex tables schema with a lot of info) you can use SINGLE_TABLE strategy. If there will be a lot of info/columns(in your tables), your data will not be normalized with this strategy so better use JOINED or TABLE_PER_CONCRETE_CLASS strategy.

Hibernate Envers - Audited Entity can't recover ID from empty audited table relation

I need some help on Hibernate Envers. I have the following scenario:
I have a Entity in Hibernate/JPA and this Entity has a normal configuration:
package com.algar.fsw.siscos.model;
/**
* TbUsuario generated by hbm2java
*/
#Entity
#Table(name = "SCCTB023_USUARIO")
#Audited
#AuditTable("SCCTB047_USUARIO_ADTRA")
public class Usuario implements java.io.Serializable {
#Id
#Column(name = "NU_USUARIO", nullable = false, scale = 0)
#NotNull
private Long id;
#Column(name = "NO_USUARIO", nullable = false, length = 50)
#NotNull
#Length(max = 50)
private String nome;
#Column(name = "NO_LOGIN", unique = true, nullable = false, length = 16)
#NotNull
#Length(max = 16)
private String login;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "NU_PERFIL", nullable = false)
private Perfil perfil = new Perfil();
#OneToMany(fetch = FetchType.LAZY, mappedBy = "usuario" , cascade = CascadeType.ALL)
private List<UsuarioGrupo> usuariosGrupos = new ArrayList<UsuarioGrupo>();
public Usuario() {
}
public Usuario(Long id ) {
this.id = id;
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public Perfil getPerfil() {
return perfil;
}
public void setPerfil(Perfil perfil) {
this.perfil = perfil;
}
public List<UsuarioGrupo> getUsuariosGrupos() {
return usuariosGrupos;
}
public void setUsuariosGrupos(List<UsuarioGrupo> usuariosGrupos) {
this.usuariosGrupos = usuariosGrupos;
}
}
And, this Entity has a relation with this Entity:
package com.algar.fsw.siscos.model;
#Entity
#Table(name = "SCCTB018_PERFIL")
#Audited
#AuditTable("SCCTB042_PERFIL_ADTRA")
public class Perfil implements java.io.Serializable {
#Id
#Column(name = "NU_PERFIL", nullable = false, scale = 0)
#NotNull
private Long id;
#Column(name = "NO_PERFIL", nullable = false, length = 30)
#NotNull
#Enumerated(EnumType.STRING)
private PerfilEnum perfilEnum;
#Column(name = "IC_ATIVO", nullable = false, precision = 1, scale = 0)
private boolean ativo = true;
#Column(name = "NU_NIVEL", nullable = false, precision = 1, scale = 0)
private Long nivel;
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getNome() {
return perfilEnum.toString();
}
public boolean isAtivo() {
return ativo;
}
public void setAtivo(boolean ativo) {
this.ativo = ativo;
}
public Long getNivel() {
return nivel;
}
public void setNivel(Long nivel) {
this.nivel = nivel;
}
public PerfilEnum getPerfilEnum() {
return perfilEnum;
}
public void setPerfilEnum(PerfilEnum perfilEnum) {
this.perfilEnum = perfilEnum;
}
}
When I retrieve the Usuario information, everything is ok, but when I call the method getPerfil I receive a NoEntityFoundException because of the ID of the relation. The data exists only in the regular table, in the audited table the data does not exist because the Audited tables were created after the data already exists in the original table so no records were putted on audited table . I need to recover only the ID, I don't need the data from Perfil. So, if I call getPerfil, the only thing I need is the ID information.
Does anyone know a workaround or some solution for this kind of problem?
The reason I need this is because of an WEB application that has a screen that shows the changes in some Entity, showing the values: date_created, property_changed, value_before and value_after. So I'm constructing the logic to compare the revisions manually in java.
Please, could anyone help me?
Thanks
You will need to make a native sql query on the table and pull the id. You'll want to pull the revision id initially (to make the query), and then re-request the data (to get the requisite id using a native query).
If you had data before starting to use Envers, you should create an initial "0" revision containing all that data.
I had similar issue and the only solution (workaround) that helped me was to manual insert all data prior to envers as initial revision 1 with zero timestamp (1970-01-01). For your case it would be something like this:
INSERT INTO REVINFO(REV, REVTSTMP) VALUES(1, 0);
INSERT INTO SCCTB047_USUARIO_ADTRA
(REV, REVTYPE, NU_USUARIO, NO_USUARIO, NO_LOGIN, NU_PERFIL)
SELECT 1, 0, NU_USUARIO, NO_USUARIO, NO_LOGIN, NU_PERFIL FROM SCCTB023_USUARIO;
INSERT INTO SCCTB042_PERFIL_ADTRA
(REV, REVTYPE, NU_PERFIL, NO_PERFIL, IC_ATIVO, NU_NIVEL)
SELECT 1, 0, NU_PERFIL, NO_PERFIL, IC_ATIVO, NU_NIVEL FROM SCCTB018_PERFIL;

Categories