How do I populate two fields of entity using Hibernate? - java

I am building an API to return two fields as such:
{
currentPoints: 325,
badgeName: "Some Badge"
}
However, I am having trouble using hibernate in order populate those two fields. I made two attempts and both are throwing errors. Both of these errors can be found in their respective Repository file. In the 2nd attempt, I am using native=true and am able to get it to work using a SELECT *. However, I am trying to only populate and return two fields of the entity.
One solution I thought about is using the 2nd approach with a SELECT * and creating another package named response with CurrentInfoResponse class and just returning that class. However, I wanted to see if there was a way to avoid this using the current model that I have.
Possible Solution:
#Getter
#AllArgsConstructor
public class CurrentInfoResponse{
private Integer currentPoints;
private String badgeName
}
Package Structure:
Controller.java:
#GetMapping("/current-badge/{userId}")
public CurrentBadgeInfoModel getCurrentBadge(#PathVariable Integer userId){
return currentBadgeInfoService.getCurrentBadge(userId);
}
ServiceImpl.java:
#Override
public CurrentBadgeInfoModel getCurrentBadge(Integer userId){
return currentBadgeInfoRepository.getCurrentBadge(userId);
}
CurrentBadgeInfoModel.java:
#Getter
#Entity
#Table(name = "user_current_badge_info")
public class CurrentBadgeInfoModel {
#Id
#Column(name = "user_current_info_id")
private Integer userCurrentBadgeInfo;
#Column(name = "user_id")
private Integer userId;
#Column(name = "current_points")
private Integer currentPoints;
#ManyToOne
#JoinColumn(name = "badge_id")
private BadgeModel badgeModel;
}
BadgeModel.java
#Getter
#Entity
#Table(name = "badge_info")
public class BadgeModel {
#Id
#JoinColumn(name= "badge_id")
private Integer badgeId;
#Column(name = "badge_name")
private String badgeName;
}
Repository.java - ATTEMPT 1:
#Repository
public interface CurrentBadgeInfoRepository extends JpaRepository<CurrentBadgeInfoModel, Integer> {
#Query("SELECT cbim.currentPoints, cbim.badgeModel.badgeName FROM CurrentBadgeInfoModel cbim JOIN
cbim.badgeModel WHERE cbim.userId=?1")
CurrentBadgeInfoModel getCurrentBadge(Integer userId);
}
//Error: No converter found capable of converting from type [java.lang.Integer] to type [com.timelogger.model.CurrentBadgeInfoModel]
Repository.java - ATTEMPT 2:
#Repository
public interface CurrentBadgeInfoRepository extends JpaRepository<CurrentBadgeInfoModel, Integer> {
#Query(value = "SELECT current_points, badge_name FROM user_current_badge_info ucbi JOIN badge_info bi ON ucbi.badge_id=bi.badge_id WHERE user_id=?1", nativeQuery = true)
CurrentBadgeInfoModel getCurrentBadge(Integer userId);
}
//Error: Column 'user_current_info_id' not found

Using the SELECT clause of HQL should help you here.
If you don't have that constructor, you can add it
#Query("SELECT new CurrentBadgeInfoModel(cbim.currentPoints, cbim.badgeModel.badgeName) FROM CurrentBadgeInfoModel cbim JOIN
cbim.badgeModel WHERE cbim.userId=?1")
Notice the usage of new CurrentBadgeInfoModel(cbim.currentPoints, cbim.badgeModel.badgeName)

I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(CurrentBadgeInfoModel.class)
public interface CurrentInfoResponse {
Integer getCurrentPoints();
#Mapping("badgeModel.badgeName")
String getBadgeName();
}
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
CurrentInfoResponse findByUserId(Integer userId);
The best part is, it will only fetch the state that is actually necessary!

Related

How should a DTO look like compared to an Entity

Let's assume an application where there are leagues and teams inside of leagues, and teams can be in multiple leagues aswell. So we do have a many to many relationship.
League Entity
#Data
#Entity
public class League {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String countryCode;
private SportType sportType;
#ManyToMany(mappedBy = "leagues")
private List<Team> teams;
}
Team Entity
#Data
#Entity
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String logoUrl;
private SportType sportType;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "team_league",
joinColumns = #JoinColumn(name = "team_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "league_id", referencedColumnName = "id"))
private List<League> leagues;
}
I am now on the point where i need for example to create a new team, and upon creation, it needs a league to put in, which has to already exist. That means i need an endpoint which takes a list of leagueIds instead of a List<League>. So i assume i need to build a DTO. But how should the DTO look like and how would i implement the method that maps the DTO to an entity and saves it to the database.
My idea of the TeamDTO
#Data
public class TeamDTO {
private Long id;
private String name;
private String logoUrl;
private SportType sportType;
private List<Integer> leagueIds;
}
So instead of a List<League> i do have a List<Integer> leagueIds so that the endpoint can accept proper JSON. Is that correct?
Now i want to create the team in the database, IF the leagues of List<Integer> leagueIds are present in the database. So my question now is, when do i map to the entity.
My idea of the implementation of the service
public class TeamServiceImpl implements TeamService {
#Autowired
private LeagueRepository leagueRepository;
#Autowired
private TeamRepository teamRepository;
#Override
public Team createTeam(TeamDTO teamDTO) {
List<Long> ids =
teamDTO.getLeagueIds().stream().filter(leagueId ->
leagueRepository.findById(leagueId).isPresent()).
collect(Collectors.toList();
if (!ids.isEmpty()) {
Team team = new Team();
team.setName(teamDTO.getName());
team.setLogoUrl(teamDTO.getLogoUrl());
team.setSportType(teamDTO.getSportType());
// do i actually need the League entities to set this?
team.setLeagues(...);
return team;
}
return null;
}
}
Most important question is: Is this the correct way?
Should i use a mapper for DTO to entity and vice versa?
Should i implement a mapper myself (i mean it only maps a few
fields)?
And on what place i would use the mapper, if i would implement one?
I don't know why you only want to save the team if it has leagues assigned that exist. It just sounds wrong to me i.e. some kind of bug is in your app if the league for an id does not exist. You should set a list of league references and rely on the FK-constraint to error if a wrong league id is used i.e. use something like this:
List<League> leagues =
teamDTO.getLeagueIds().stream().map(leagueId ->
leagueRepository.getOne(leagueId)).
collect(Collectors.toList());
team.setLeagues(leagues);
The DTO approach is fine and as long as it stays this simple, I guess using this custom implementation is good enough. If you have more complex requirements and want to make use of more efficient processing I would recommend you look into Blaze-Persistence Entity-Views which was made for exactly this purpose, efficient mapping between JPA entities and DTOs.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Team.class)
#UpdatableEntityView
public interface TeamDTO {
#IdMapping
Long getId();
String getName();
void setName(String name);
String getLogoUrl();
void setLogoUrl(String logoUrl);
SportType getSportType();
void setSportType(SportType sportType);
#UpdatableMapping
#JsonIgnore
List<LeagueDto> getLeagues();
default List<Long> getLeagueIds() {
return getLeagues().stream().map(LeagueDto::getId).collect(toList());
}
default void setLeagueIds(List<Long> ids) {
getLeagues().clear();
ids.stream().map(id -> evm().getReference(LeagueDto.class, id)).forEach(getLeagues()::add);
}
// This is a special context providing method
EntityViewManager evm();
#EntityView(League.class)
interface LeagueDto {
#IdMapping
Long getId();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
TeamDTO a = entityViewManager.find(entityManager, TeamDTO.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<TeamDTO> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
The saving part will then be as simple as this:
public class TeamServiceImpl implements TeamService {
#Autowired
private TeamRepository teamRepository;
#Override
public Team createTeam(TeamDTO teamDTO) {
teamRepository.save(teamDTO);
return teamRepository.getOne(teamDTO.getId());
}
}
Due to the change-tracking implementation of Entity-Views, at any point in time it is clear what is dirty and will by default only flush these changes and avoid unnecessary select statements during flushing.

ListJoin and multiselect with criteriaquery with Polymorphism

I was wondering if anyone knows the cause of the Exception being thrown? I have the following entities below. Is it because some Employees returned aren't DriverEmployees and thus do not have routes?
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
private String name;
}
#Entity
public class DriverEmployee extends Employee {
#OneToMany(fetch = FetchType.LAZY)
private List<Routes> routes;
}
#Entity
public class Routes {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
private String name;
private String description;
}
CriteriaQuery<Tuple> criteria = criteriaBuilder.createQuery(Tuple.class);
Root<Employee> employee = criteria.from(Employee.class);
Root<DriverEmployee> driverEmployee = criteriaBuilder.treat(employee, DriverEmployee.class);
ListJoin<DriverEmployee, Routes> routes = driverEmployee.joinList("routes");
// Want routes to be returned as a list in the multiselect.
criteria.multiselect(employee.get("name").alias("name"), routes.alias("routes"));
TypedQuery<Tuple> query = em.createQuery(criteria);
query.getResultList().forEach((t) -> {
process(t.get("name", String.class));
processList(t.get("routes", List.class).size());
});
The error I'm receiving is below. Anyone know how I can get the following to run successfully?
Caused by: java.lang.IllegalStateException: No data type for node: org.hibernate.hql.internal.ast.tree.IdentNode
+-[IDENT] IdentNode: 'routes' {originalText=routes}
In this current example you have tuple which contains one object of DriverEmployee and one object of Routes (I recommend you to rename this entity to Route and set #Table(name = "routes"). According to JavaDoc of multiselect():
If the type of the criteria query is CriteriaQuery (i.e., a criteria query object created by either the createTupleQuery method or by passing a Tuple class argument to the createQuery method), a Tuple object corresponding to the arguments of the multiselect method, in the specified order, will be instantiated and returned for each row that results from the query execution.
So it means that you CAN'T make a Tuple like Tuple<DriverEmployee, List<Routes>>
The way how you can reach such behavior of your current query is to do it by yourself. For instance:
your method with criteria:
CriteriaQuery<Tuple> criteria = criteriaBuilder.createQuery(Tuple.class);
Root<Employee> employee = criteria.from(Employee.class);
Root<DriverEmployee> driverEmployee = criteriaBuilder.treat(employee, DriverEmployee.class);
ListJoin<DriverEmployee, Routes> routes = driverEmployee.joinList("routes");
criteria.multiselect(employee.get("name").alias("name"), routes.alias("routes"));
TypedQuery<Tuple> query = em.createQuery(criteria);
List<Tuple> resultList = query.getResultList();
Map<String, List<Routes>> resultMap = getMapFromResultList(resultList);
resultMap.entrySet().forEach((name, routesList) -> {
process(name);
processList(routesList);
});
and the method of obtaining the map:
private Map<String, List<Routes>> getMapFromResultList(List<Tuple> tuples) {
Map<String, List<Routes>> map = new HashMap<>();
tuples.forEach(tuple -> {
String name = tuple.get("name", String.class);
Routes routes = tuple.get("routes", Routes.class);
map.computeIfPresent(name, (key, value) -> {
value.add(routes);
return value;
});
map.computeIfAbsent(name, (key) -> {
List<Routes> routesList = new ArrayList<>();
routesList.add(routes);
return routesList;
});
});
return map;
}
I guess the Hibernate JPA Criteria implememtation doesn't support that. If you really want to use the JPA Criteria API to do this, you are probably out of luck. In JPQL/HQL you could model this like SELECT e.name, r FROM DriverEmployee e LEFT JOIN e.routes r. On top of that you have to extract the values like Andrew Kolesnyk mentioned.
However, this is is the perfect use case for Blaze-Persistence Entity Views.
Blaze-Persitence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A mapping for your model could look as simple as the following
#EntityView(DriverEmployee.class)
interface EmployeeDto {
#IdMapping
Integer getId();
String getName();
List<RoutesDto> getRoutes();
}
#EntityView(Routes.class)
interface RoutesDto {
#IdMapping
Integer getId();
String getName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
EmployeeDto dto = entityViewManager.find(entityManager, EmployeeDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features and it can also be saved back. Here a sample repository
#Repository
interface EmployeeRepository {
EmployeeDto findOne(Long id);
}
It will only fetch the mappings that you tell it to fetch.
Here you can see an example project: https://github.com/Blazebit/blaze-persistence/tree/master/examples/spring-data-webmvc

Select only some columns from a table

Is there a way to select only some columns from a table using jpa?
My tables are huge and I am not allowed to map all the columns in my entities. I tried to create an entity (as a side note, I don't have PKs in my tables):
#Entity
#Table(name = "SuperCat")
#Getter
#Setter
public class Cat{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name="nameCat")
private String name;
}
and then in my repository to
public interface CatRepository extends
CrudRepository<Cat, Long> {
#Query(
"SELECT name FROM Cat")
Page<Cat> getAlCats(Pageable pageable);
This is only a simple example, but the idea is the same. I have searched a lot and I found projections, but there you need to map the whole table, then I found native queries, but still doesn't apply. I know I can return an Object and the other solution is to use query with NEW and create my own object (no #entity, like a pojo). But is there a way that I can do this using jpa, to be able to use repository and services, if I am creating my own pojo then i will create a #transactional class put the queries (with NEW) there and this is it. I don't like this approach and I don't think that the jpa does't allow you to select only some columns, but I didn't find a proper way.
Maybe you will ask what is the result if I am doing like this:
I get this error: "Cannot create TypedQuery for query with more than one return using requested result type [java.lang.Long]"
(For new queries, I am talking about : http://www.java2s.com/Tutorials/Java/JPA/4800__JPA_Query_new_Object.htm maybe I was not clear)
You can do the same by using below approach.
Just create a constructor in entity class with all the required parameters and then in jpa query use new operator in query like below.
String query = "SELECT NEW com.dt.es.CustomObject(p.uniquePID) FROM PatientRegistration AS p";
TypedQuery<CustomObject> typedQuery = entityManager().createQuery(query , CustomObject.class);
List<CustomObject> results = typedQuery.getResultList();
return results;
And CustomObject class should look like below with the constructor.
public class CustomObject {
private String uniquePID;
public CustomObject(String uniquePID) {
super();
this.uniquePID = uniquePID;
}
public String getUniquePID() {
return uniquePID;
}
public void setUniquePID(String uniquePID) {
this.uniquePID = uniquePID;
}
}
spring-data-jpa projection not need to map the whole table, just select the necessary fileds :
// define the dto interface
public interface CatDto {
String getName();
// other necessary fields
...
}
#Query(value = "select c.name as name, ... from Cat as c ...)
Page<CatDto> getAllCats(Pageable pageable);
By this way, CatDto is an interface and it only includes some fileds part of the whole table. Its fields name need to match the select field's alias name.

Query using value from nested interface as parameter

I have been reading a lot about using enums as parameters in queries. I have some queries in my project that use the value from these enums as parameters.
For example:
public enum YesNo {
Y, N
}
Query:
select ent
from
Entity ent
where
ent.id = :id
and ent.deleted = project.path.example.YesNo.N
Entity:
#Entity
public class Entity{
Long id;
#Enumerated(EnumType.STRING)
YesNo deleted;
}
The above works correctly as expected.
However, when I have the following:
interface Commons{
interface MostCommonTypesofAnimals {
long DOG = 1L;
long CAT = 2L;
}
}
Query
select a
from
Animal a
where
a.id = :id
and a.type = project.path.example.Commons.MostCommonTypesofAnimals.DOG
Entity
#Entity
public class Animal{
Long id;
Type type;
}
#Entity
public class Type{
public Long id;
}
It does not work telling me that the path is incorrect even though it is actually correct.
Is there any work around? Or interface values cannot be mapped? Can anyone provide me an example that works? I could not find anything similar.
Please note that this is just an example to illustrate the situation., those are not the real names that I am using or anything.
For using enum while using hibernate / jpa (based on your tags), you should use annotation in your Pojo class.
#Enumerated(EnumType.ORDINAL)
In your example, something like:
#Entity
#Table(name = "tableName")
public class entityName {
#Enumerated(EnumType.ORDINAL)
private YesNo yesNoEnum;
}
The annotation can go here or in the getter, as you prefer.
You can find more info here
ps: for yes or no I suggest you using a boolean value, not an enum

Java persistence mapped superclass with optional properties

I'm using the javax.persistence package to map my Java classes.
I have entities like these:
public class UserEntity extends IdEntity {
}
which extends a mapped superclass named IdEntity:
#MappedSuperclass
public class IdEntity extends VersionEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// Getters and setters below...
}
The IdEntity super class extends another mapped super class named VersionEntity to make all entities inherit version properties:
#MappedSuperclass
public abstract class VersionEntity {
#Version
private Integer version;
// Getters and setters below...
}
Why?
Because now I can make generic queries on the IdEntity class for all entities, and it will look like this: (example)
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<IdEntity> criteria = builder.createQuery(IdEntity.class);
Now to the problem.
Some of my entities will have timestamps like created_at and deleted_at. But not all entities.
I could provide these properties in my entity classes like this:
public class UserEntity extends IdEntity {
#Basic(optional = false)
#Column(name = "updated_at")
#Temporal(TemporalType.TIMESTAMP)
private Date updatedAt;
}
But as I have a lot of entities, this will make me put a lot of redundant code in all entities that should have timestamps. I wish there was some way I could make the relevant classes inherit these fields in some way.
One possible solution is to create a parallell IdEntity superclass, maybe named IdAndTimeStampEntity and make those entities that should have timestamps inherit from this new superclass instead, but hey that's not fair to my colleague-developers because now they have to know which super class to choose from when writing generic queries:
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<???> criteria = builder.createQuery(???); // Hmm which entity should I choose IdEntity or IdAndTimeStampEntity ?? *Annoyed*
And the generic entity queries become not so generic..
My question: How can I make all of my entities inherit id and
version fields, but only a sub part of all entities inherit
timestamp fields, but keep my queries to a single type of entities?
Update #1
Question from Bolzano: "can you add the code which you specify the path(holds table info) for entities ?"
Here is a working example of querying a UserEntity which is a IdEntity
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<IdEntity> criteria = builder.createQuery(IdEntity.class);
Root<IdEntity> from = criteria.from(IdEntity.class);
criteria.select(from);
Path<Integer> idPath = from.get(UserEntity_.id); //generated meta model
criteria.where(builder.in(idPath).value(id));
TypedQuery<IdEntity> query = JPA.em().createQuery(criteria);
return query.getSingleResult();
I would pick a solution that didn't enforce a class-based object model like you've outlined. What happens when you don't need optimistic concurrency checking and no timestamps, or timestamps but no OCC, or the next semi-common piece of functionality you want to add? The permutations will become unmanageable.
I would add these common interactions as interfaces, and I would enhance your reusable find by id with generics to return the actual class you care about to the caller instead of the base superclass.
Note: I wrote this code in Stack Overflow. It may need some tweaking to compile.
#MappedSuperclass
public abstract class Persistable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// getter/setter
}
public interface Versioned {
Integer getVersion();
}
public interface Timestamped {
Date getCreated();
Date getLastUpdated();
}
#Embeddable
public class TimestampedEntity {
#Column(name = "create_date")
#Temporal
private Date created;
#Column
#Temporal
private Date lastUpdated;
// getters/setters
}
#Entity
public class UserEntity extends Persistable implements Versioned, Timestamped {
#Version
private Integer version;
#Embedded
private TimestampedEntity timestamps;
/*
* interface-defined getters. getTimestamps() doesn't need to
* be exposed separately.
*/
}
public class <CriteriaHelperUtil> {
public <T extends Persistable> T getEntity(Class<T> clazz, Integer id, SingularAttribute idField) {
CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(clazz);
Root<T> from = criteria.from(clazz);
criteria.select(from);
Path<Integer> idPath = from.get(idField);
criteria.where(builder.in(idPath).value(id));
TypedQuery<T> query = JPA.em().createQuery(criteria);
return query.getSingleResult();
}
}
Basic Usage:
private UserEntity ue = CriteriaHelperUtil.getEntity(UserEntity.class, 1, UserEntity_.id);
ue.getId();
ue.getVersion();
ue.getCreated();
// FooEntity implements Persistable, Timestamped
private FooEntity fe = CriteriaHelperUtil.getEntity(FooEntity.class, 10, FooEntity_.id);
fe.getId();
fe.getCreated();
fe.getVersion(); // Compile Error!
#MappedSuperclass
public class IdEntity{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Version
private Integer version;
}
#MappedSuperclass
public class IdAndTimeStampEntity extends IdEntity{
Date created;
}
#Entity
public class UserEntity extends IdAndTimeStampEntity{
String name;
}
#Entity
public class FooEntity extends IdEntity{...
Pros of this solution:
In simple and clear way uses OOP without need to embed duplicate code implementing intefaces in every subclass. (Every class is also interface)
Optimistic locking version column is mostly used approach. And should be part of base class. Except read only entities like codetables.
Usage:
public <T extends IdEntity> T persist(T entity) {
if (entity instanceof IdAndTimeStampEntity) {
((IdAndTimeStampEntity) entity).setCreated(new Date());
}
if (!em.contains(entity) && entity.getId() != null) {
return em.merge(entity);
} else {
em.persist(entity);
return entity;
}
}
I wish there was some way I could make the relevant classes inherit these fields in some way.
You could make a custom annotation #Timed and use an annotation processor to add the timestamp field and annotations, either by using a bytecode manipulation framework or creating a delegating subclass. Or, for example if you use Lombok, create a Lombok annotation.
That way, your team members only have to remember to use the #Timed annotation when you have entities with timestamps. Whether you like such approach or not is up to you.

Categories