I have designed the entities in my application to follow the Hibernate's inheritance strategy Inheritance.JOINED.
The base abstract class is UserTable and the concrete derived classes are ClientTable and the OwnerTable.
The thing that I want to achieve is to have a single repository in which I could find any UserTable (both ClientTable and OwnerTable) by the Id or Email. The important requirement is that they can be cast to the correct type once they are fetched, and that they carry their specific fields (and not just the ones inherited from UserTable).
Equally important, I should be able to persist those entities through the same repository, ie. repository.save(clientTable).
So something like this should be possible:
UserTableRepository repository = // ...
int clientId = 3;
int ownerId = 5;
ClientTable clientTable = (ClientTable) repository.findByUserId(clientId);
OwnerTable clientTable = (OwnerTable) repository.findByUserId(ownerId);
clientTable = (ClientTable) repository.findByEmail(clientEmail);
ownerTable = (OwnerTable) repository.findByEmail(ownerEmail);
My entities look like this.
UserTable.java
#Entity
#Table(name = "USER")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class UserTable implements Serializable {
#Column(name = "USER_ID", nullable = false)
private Long userId;
#EmbeddedId
private UserTablePK userTablePK;
#Column(name = "PASSWORD", nullable = false, length = 512)
private String password;
#Column(name = "FIRSTNAME", nullable = false, length = 256)
private String firstName;
// get, set
}
.. and a primary key
UserTablePK.java
#Embeddable
public class UserTablePK implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "EMAIL", nullable = false, length = 256)
private String email;
public UserTablePK() {
}
public UserTablePK(String email) {
this.email = email;
}
// get, set
}
ClientTable.java
#Entity
#Table(name = "CLIENT")
public class ClientTable extends UserTable implements Serializable {
#Column(name = "SHOPING_POWER")
private Integer shoppingPower;
#Column(name = "SHOPPING_FREQUENCY")
private Integer shoppingFreq;
// get, set
}
OwnerTable.java
#Entity
#Table(name = "OWNER")
public class OwnerTable extends UserTable implements Serializable {
#Column(name = "EFFICIENCY")
private String efficiency;
#Column(name = "SALARAY")
private Integer;
// get, set
}
Now once the entities are set, I need to write a repository that would act like described above, and here is where I need some help. I was thinking to write something like this.
UserTableRepository.java
#Repository
public interface UserTableRepository extends CrudRepository<UserTable, UserTablePK> {
#Query("SELECT e FROM UserTable e WHERE e.userTablePK.email = ?1")
public UserTable findByEmail(String email); // if owner email, retrieve OwnerTable; if client email, retrieve ClientTable instance
#Query("SELECT e FROM UserTable e WHERE e.userId = ?1")
public UserTable findByUserId(Long userId); // if owner id, retrieve OwnerTable; if client id, retrieve ClientTable instance
}
I didn't actually try this yet, because I want to check if this is even the right route to take, but: I am rather skeptical that this Query would work. Because in order to retrieve the whole subclass object, some kind of JOIN should be used, and the problem is that I can't explicitly say ie. JOIN ClientTable because then I would not be able to fetch any OwnerTable entities.
How can I achieve that I can fetch both subclasses from the same repository?
UPDATE
Also, the requirement is to be able to fetch for the specific subclass. So something like:
List<ClientTable> clients = repository.findAllClients();
Is that possible using the same repository, or should I write repositories specific for subclasses? For example.
#Repository
public interface ClientTableRepository extends CrudRepository<ClientTable, ClientTablePK> {
// empty
}
... and then call them like:
ClientTableRepository repository = // ...
List<ClientTable> clients = repository.findAll();
Would that be enough for the JPA to determine how to find all the instances of just a specific subclass?
What you want to achieve is exactly how inheritance in JPA works.
So your queries are perfectly fine and you will have instances of the subclasses in the result list.
Read more about JPA inheritance in one of my blog posts:
https://72.services/inheritance-in-jpa/
Related
I have a doubt about how the modeling of my entity would be. Come on, I have a table in the database that serves to save documents from my system, this table has the columns id, fk_id (element foreign key), fk_table (entity name) and file_name (stores the name of my file) .
I did a lot of research before posting my question here, but I didn't find anything related to it, what would my entities, user, patient and doctor?
DB:
id
fk_id
fk_table
file_name
1
21
user
test1.jpg
2
32
doctor
test2.pdf
3
61
user
test10.pdf
4
100
patient
test5.jpg
Class:
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String LastName;
// What would a one-to-many relationship look like?
}
public class patient{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// What would a one-to-many relationship look like?
}
You can use #Where. But be aware that #Where is a Hibernate annotation. It's not in the JPA standard.
For example in the User entity: (I assume that your table is mapped to an entity called Document)
#Where( clause = "fk_table = 'user'")
#JoinColumn(name = "fk_id")
#OneToMany
private List<Document> documents = new ArrayList<>( );
The following is based only on standard JPA annotations. The idea is to create an inheritance hierarchy for the documents table. The base is:
#Entity
#Table(name = "XX_DOCUMENT")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "fk_table")
public abstract class BaseDocument {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#Column(name = "file_name")
private String fileName;
}
Here we define that all entities extending this will go to the same table, with the fk_table column to discriminate. The entities extending it are defined as follows:
#Entity
#DiscriminatorValue("doctor")
public class DoctorDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Doctor doctor;
}
#Entity
#DiscriminatorValue("patient")
public class PatientDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Patient patient;
}
// and so on
The interesting thing is that we are reusing the column fk_id to point to the right table. From a small experiment, Hibernate seems to not have problems with it. I would suggest that you manage the DB creation another way just to be safe.
The Doctor, Patient etc need not have a common base class, e.g.:
#Entity
#Table(name = "XX_DOCTOR")
public class Doctor {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "doctor")
private Collection<DoctorDocument> documents = new ArrayList<>();
// any doctor-specific fields
}
#Entity
#Table(name = "XX_PATIENT")
public class Patient {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "patient")
private Collection<PatientDocument> documents = new ArrayList<>();
// any patient-specific fields
}
// and so on
You can read a (doctor, patient, ...)'s documents from the relevant collection. You can even query BaseDocument instances based on any criteria.
You can even go ahead and do more fabcy stuff with the Java code. E.g. define an interface HasDocuments:
public interface HasDocuments<D extends BaseDocument> {
Collection<D> getDocuments();
}
Doctor, Patient, ..., implements this, so they can all be treated the same way.
Have a "full Entity" class:
#Entity(name = "vacancy_dec_to_words")
public class VacancyDescriptionToWords {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#JoinColumn(name = "vacancy_description_id")
#ManyToOne(cascade = CascadeType.ALL)
private VacancyDescription vacancyDescription;
#JoinColumn(name = "words_id")
#ManyToOne
private Words words;
#Column(name = "qty")
private int qty;
#Column(name = "create_date")
private Date date;
//...getters and setters
In some methods I need use only 2 column from this database: word_id and qty
I try the follow way:
Projections
https://docs.spring.io/spring-data/jpa/docs/2.1.2.RELEASE/reference/html/#projections
public interface QtyWords {
Long getWords();
Integer getQty();
}
JpaReposytory:
*Query, that I use tested and it workable, I use him in JpaRepository:
#Repository
public interface SmallVDTWRepository extends JpaRepository<VacancyDescriptionToWords, Long> {
#Query(nativeQuery = true,
value = "SELECT sum(qty), words_id FROM vacancy_desc_to_words WHERE vacancy_description_id IN (" +
"SELECT id FROM vacancy_description WHERE vacancy_id IN (" +
"SELECT id FROM vacancy WHERE explorer_id = :exp))" +
"GROUP BY words_id")
List<QtyWords> getDistinctWordsByExplorer(#Param("exp") long exp);
}
But I get some interesting result when I get list of entities:
List<QtyWords> list = vdtwService.getByExplorerId(72);
I am not get any exceptions, but I have the list with are unknowns objects. This objects contains my data, which I need(qty and words_id), but I cannot get them from him.
Can I use this method (Projection) to implement this task and, in general, how to correctly implement the 'Light Entity' in this case?
Spring provides two mechanisms that can be used to limit data to be fetched.
Projections
Projections can help you to reduce data, retrieved from database, by setting what exactly attributes you want to fetch.
Example:
#Entity
class Person {
#Id UUID id;
String firstname, lastname;
#OneToOne
Address address;
}
#Entity
static class Address {
#Id UUID id;
String zipCode, city, street;
}
interface NamesOnly {
String getFirstname();
String getLastname();
}
#Repository
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
Entity graph
Annotation EntityGraph can help you to reduce amount of queries to database, by setting what exactly related entities you need to fetch.
Example:
#Entity
#NamedEntityGraph(name = "GroupInfo.detail", attributeNodes = #NamedAttributeNode("members"))
public class GroupInfo {
#Id UUID id;
#ManyToMany //default fetch mode is lazy.
List<GroupMember> members = new ArrayList<GroupMember>();
}
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name); //Despite of GroupInfo.members has FetchType = LAZY, it will be fetched because of using EntityGraph
}
There are two types of EntityGraph:
EntityGraphType.LOAD - is used to specify an entity graph, attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated according to their specified or default FetchType.
EntityGraphType.FETCH - is used to specify an entity graph, attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated as FetchType.LAZY.
PS: Also remember that you can set lazy fetch type: #ManyToOne(fetch = FetchType.LAZY) and JPA will not fetching child entities when parent is being fetched.
I know the title may sound a bit confusing, but I did not know how to summarize my problem on the title.
My main problem is that I do not want to return an specific column using PagingAndSortingRepository. Imagine the following scenario:
I have two entitys, one called Users:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank()
#Column(name = "name")
private String name;
#Column(name = "password")
private String password;
}
And one called Cars:
#Entity
#Table(name = "car")
public class Car implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank()
#Column
private String name;
#ManyToOne(targetEntity = User.class, fetch = FetchType.EAGER, cascade = CascadeType.MERGE)
#JoinColumn(name = "owner_id", referencedColumnName = "id")
private User owner;
}
I want to return a list of car by user's name using PagingAndSortingRepository. Using the following interface I can achieve that:
public interface CarRepository extends PagingAndSortingRepository<Car, Long> {
List<Car> findByOwner_name(String name);
}
However, with said interface the result will also return the user's password on it. I could use a for on each element of the list and set the password as null, but it would be troublesome in a scenario where there is a user with lots of cars. How could I make it so that the result of the list come with the specific column, password, without any value to the user?
Thanks in advance =).
ps: I thought about returning a list of projection instead of a list of Car, but I do not know how to say it to the projection that just one attribute from the class should be null.
You need to use Projections for this purpose. It is documented in detail here
Create an interface with fields you want to include in the entity to be returned.
For example, in your case it would be like this
interface CarSummary {
String getName();
Long getId();
UserSummary getUser();
interface UserSummary {
String getName();
Long getId();
}
}
and modify your repository like this
public interface CarRepository extends PagingAndSortingRepository<Car, Long> {
Collection<CarSummary> findByOwner_name(String name);
}
Projections can be used recursively. Something like this should work:
interface CarPojection{
Long getId();
String getName();
UserSummary getUser();
interface UserSummary {
Long getId();
String getName();
}
}
And your repository should return CarPojection
Find more information in Spring docs:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
You can return a subset of data by providing a matching constructor in your entity class.
#Query("select new com.foo.bar.entity.User(u.id, u.name) from User u where u.name = ?1")
List<User> findByOwner_name(String name);
In you class User just provide the constructor and other properties will be left null:
public User(Long id, String name) {
// ...
}
I have 2 entities in my DB with one-to-one one directional mapping:
User and PasswordResetToken. The idea behind this is to create new token each time user requests password reset and store only the latest one.
Below are my entities:
#Entity
#Table(name = "USERS")
#Getter #Setter
public class User implements Serializable {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "usersSeq")
#SequenceGenerator(name = "usersSeq", sequenceName = "SEQ_USERS", allocationSize = 1)
private long id;
#Column(name = "NAME")
private String name;
#Column(name = "PASSWORD")
private String password;
#Column(name = "EMAIL")
private String email;
#Column(name = "ROLE")
private Integer role;
}
///...
#Entity
#Table(name = "PASSWORD_RESET_TOKENS")
#Getter
#Setter
public class PasswordResetToken implements Serializable {
private static final int EXPIRATION = 24;
#Column(name = "TOKEN")
private String token;
#Id
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(nullable = false, name = "user_id")
private User user;
#Column(name = "EXPIRY_DATE")
private Instant expiryDate;
public PasswordResetToken() {
}
public void setExpiryDate(ZonedDateTime expiryDate) {
this.expiryDate = expiryDate.plus(EXPIRATION, ChronoUnit.HOURS).toInstant();
}
}
Also, I have DTOs created for both of them to pass them around my app.
Code snippets:
#Getter #Setter
public class PasswordResetTokenModel {
private String token;
private ZonedDateTime expiryDate;
private UserModel user;
}
UserModel is also used for Spring Security
#Getter
#Setter
public class UserModel extends User {
public UserModel(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
private long id;
private String name;
public String getEmail() {
return this.getUsername();
}
}
For population I've created 2 populators:
#Component
public class UserPopulatorImpl implements UserPopulator {
#Autowired
UserDetailsService userDetailsService;
#Override
public UserModel populateToDTO(User user) {
UserModel userModel = new UserModel(user.getEmail(), user.getPassword(), userDetailsService.getAuthorities(user.getRole()));
userModel.setId(user.getId());
return userModel;
}
#Override
public User populateToDAO(UserModel userModel) {
User user = new User();
user.setEmail(userModel.getEmail());
user.setName(userModel.getName());
user.setPassword(userModel.getPassword());
//TODO: change it!
user.setRole(1);
return user;
}
}
//...
#Component
public class PasswordResetTokenPopulatorImpl implements PasswordResetTokenPopulator {
#Autowired
UserPopulator userPopulator;
#Override
public PasswordResetTokenModel populateToDTO(PasswordResetToken passwordResetToken) {
PasswordResetTokenModel passwordResetTokenModel = new PasswordResetTokenModel();
passwordResetTokenModel.setUser(userPopulator.populateToDTO(passwordResetToken.getUser()));
passwordResetTokenModel.setToken(passwordResetToken.getToken());
passwordResetTokenModel.setExpiryDate(ZonedDateTime.ofInstant(passwordResetToken.getExpiryDate(), ZoneId.systemDefault()));
return passwordResetTokenModel;
}
#Override
public PasswordResetToken populateToDAO(PasswordResetTokenModel passwordResetTokenModel) {
PasswordResetToken passwordResetToken = new PasswordResetToken();
passwordResetToken.setExpiryDate(passwordResetTokenModel.getExpiryDate());
passwordResetToken.setUser(userPopulator.populateToDAO(passwordResetTokenModel.getUser()));
passwordResetToken.setToken(passwordResetTokenModel.getToken());
return passwordResetToken;
}
}
I'm saving object using
sessionFactory.getCurrentSession().saveOrUpdate(token);
When I use this code, I'm getting following exception
object references an unsaved transient instance - save the transient instance before flushing: com.demo.megaevents.entities.User
There are currently 2 issues in this code:
Seems like Cascade.ALL in my OneToOne mapping is not working. If
I create separate primary key in Token class everything works almost
as expected but storing every created token in DB (more like
OneToMany relation), however I want to avoid it as I need to store
only one token per user in my DB
I don't like using new in populators, as it forces hibernate to create new object while flushing session. However, I also don't want to do another select to fetch this data from DB because just before mentioned populator I already do this query to fetch it and I think that it's an overhead.
Also, I really want to have DTOs and I don't want to remove DTO layer.
So, my questions:
What is the correct way to handle population between DTO and entities?
Are there any other improvements (probably architectural) to my solution?
Thanks a lot.
I'm not sure why you would let UserModel extend User, but I guess you did that because you didn't want to have to copy all properties from User into UserModel. Too bad, because that's what is going to be needed to have a clean separation between the entity model and data transfer model.
You get that exception because you try to persist a PasswordResetToken that has a reference to a User object with an id, but the User isn't associated with the current session. You don't have to query the user, but at least association it with the session like this:
PasswordResetToken token = // wherever you get that from
Session s = sessionFactory.getCurrentSession();
token.setUser(s.load(User.class, token.getUser().getId());
s.persist(token);
Cascading would cause the User to be created/inserted or updated via a SQL INSERT or UPDATE statement which is apparently not what you want.
You could do the Session.load() call in you populators if you want, but I'd not do that. Actually I would recommend not having populators at all, but instead create the entity objects in your service instead.
Normally you only have a few(mostly 1) ways of actually creating a new entity object, so the full extent of the transformation from DTO to entity will only be relevant in very few cases.
Most of the time you are going to do an update and for that, you should first select the existing entity and apply the fields that are allowed to be changed from the DTO on the entity object.
For providing the presentation layer with DTOs I would recommend using Blaze-Persistence Entity Views to avoid the manual mapping boilerplate and also improve performance of select queries.
I have followed this blog-tutorial and successfully got it to work:
http://jugojava.blogspot.com/2011/02/jdbc-security-realm-with-glassfish-and.html
I have named my two entities Group and User. The have a bi-directional many-to-many relationship.
Now the reason I have done it as in the blog is because I am making an administrator page where I want to be able to add new users. I also let users have the oppertunity to register them self, and they will have the role user.
#Entity
public class Group implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String groupName;
#ManyToMany
#JoinTable(joinColumns = {#JoinColumn(name="group_id")}, inverseJoinColumns = {#JoinColumn(name="user_id")})
private List<User> users;
....
}
and
#Entity
#Table(name = "app_user")
public class User implements Serializable {
public static final String ALL = "User.all";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private Integer age;
private String username;
private String email;
private String password;
#ManyToMany(mappedBy = "users")
private List<Group> groups;
....
}
My question is how I would assign the group user to a user when it register without me picking the groups from a list in the view?
This is what I have done in the application code but it binds the code the the id of the group in the database, are there better ways?
Method from EJB
public void persistAsUser(User user) {
Group group = new Group(2L, "user");
user.addGroup(group);
userRepository.persist(user);
}
You may want to define a UNIQUE index on the field groupName. Then, create a Data Access Object for the Group table, which provides a method for getting a Group from a groupName (code not tested):
public class GroupDAO implements Serializable {
#PersistenceContext private EntityManager em;
public Group findByGroupName(String groupName) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Group> cq = cb.createQuery(Group.class);
Root<Group> group = cq.from(Group.class);
cq.where(cb.equal(group.get(Group_.groupName), groupName));
TypedQuery<Group> q = em.createQuery(cq);
return q.getSingleResult();
}
}
If you don't like Criteria Builder, you can use a Named Query. Add this annotation to your Group Entity Class:
#NamedQuery(name = "Group.findByGroupname", query = "SELECT f FROM group f WHERE f.groupname = :groupname")
and build a Named Query as follows:
return em.createNamedQuery("Group.findByGroupname").setParameter("groupname", groupName).getResultList();