JPA Add extra fields by calling a function - jpa

How can I add an extra column in JPA query?
Suppose we have a table "users" created by following pseudocode:
create table users(name,birthdate);
User jpa entity is something like this :
#Entity
#Table("users")
public class User {
#Basic
#Column
private String name;
#Basic
#Column
private Date birthDate;
#Transient
private int age;
//getters and setters are here
}
Note that the Age field is not exsists in the table
I want to write the following query in JQL using
entityManager.createQuery(jql)
and calculate age over the database
select u.*, sysdate-u.birthdate age from users

The problem is solved.
The query text is like :
String jql = "select NEW entity.User(u.id, u.name, u.birthDate, sysdate-u.birthDate as age) from User u";
and also a appropriate constructor is added to the entity
public User(int id, String name, Date birthDate, double age) {
this.id = id;
this.name = name;
this.birthDate = birthDate;
this.age = age;
}
and uses entitymanaget.createQuery(jql) instead of createNativeQuery

Usully i solve this problem using this way :
select new name.of.package.users(u.*, sysdate - u.birthdate as age) from users u;
Edit
It leads to ORA-00923: FROM keyword not found where expected My Query
text is : String jql = "select new entity.User(u.*, sysdate-u.birthdate as age) from User u";
The query contain an error in :
select new entity.User(u.*, sysdate-u.birthdate as age) from User u
//--------------------------------^^^

Possibly using a DB-View might help. Do you want the entity to be updateable? Would it be reasonable to have a "readonly"-entity derived from a kind of "maintenance"-entity which would contain the calculated columns by using a database-view as #Table?
create view usersview as name, birthdate, sysdate - birthdate as age from users;
Entity:
#Entity
#Table("usersview")
public class UserExtended extends User {
If you don't want two different Entity-Classes (even when one is derived from the other), there would be some exploration necessary how JPA allows updating on views, if the dbms supports it (oracle does), I have no experience in that, sorry.

Related

How to return a count column not exists in table by JPA

I want find a way to get extra column that count my records and return it in 1 mapping entity with extra filed.
I tried #transient on field but it will not return value when query.
Then I remove #transient but get an exception when save.
Also I tried #Formula but received null pointer exception.
Here's my repository code:
#Query(value = "select id,account,session_id,create_time,count from query_history a join " +
"(select session_id sessionId,max(create_time) createTime,count(*) count from query_history group by session_id) b " +
"on a.session_id = b.sessionId and a.create_time = b.createTime where account = ?1 order by create_time desc",
countQuery = "select count(distinct(session_id)) from query_history where account = ?1",
nativeQuery = true)
Page<QueryHistory> findByNtAndGroupBySessionAndAction(String account, Pageable pageable);
entity code:
#Entity
#Table(name = "query_history")
#Data
public class QueryHistory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String account;
#Column
private Long sessionId;
#Column
private long createTime;
#Transient
private Integer count;
}
Sorry about my English and thanks a lot for any advice.
I solved the problem by projections spring-data-projections, in fact I tried this before but in my sql:
select id,account,session_id,create_time,count
which should be:
select id,account,session_id sessionId,create_time createTime,count
PS:
projection interface:
public interface QueryHistoryWithCountProjection {
Long getId();
String getAccount();
Long getSessionId();
long getCreateTime();
Integer getCount();
}

Crieria API query using criteriabuilder.construct with a non existing relationship

Given this very simple DTO:
#Entity
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
#OneToOne
private Employee boss;
}
I'd like to make a query that gathers all employee names and their boss' id, put in a nice clean POJO:
public class EmployeeInfo {
private String name;
private Long bossId;
public EmployeeInfo(String name, Long bossId) {
this.name = name;
this.bossId = bossId;
}
}
This query should be of use:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<EmployeeInfo> query = cb.createQuery(EmployeeInfo.class);
Root<Employee> root = query.from(Employee.class);
query.select(
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
root.get("boss").get("id").as(Long.class)));
result = em.createQuery(query).getResultList();
When a bossId is present in the employee column this works just fine. But when no boss id is set the record will be completly ignored. So how do i treat this non existing boss relation as null or 0 for the construct/multiselect?
In pure SQL it is easy:
SELECT name, COALESCE(boss_id, 0) FROM EMPLOYEE;
But for the love of god i cannot make the criteria api do this.
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
cb.coalesce(root.get("boss").get("id").as(Long.class), 0L)));
The problem is that root.get("boss") generate query with cross join like this from Employee employee, Employee boss where employee.boss.id=boss.id. So records where employee.boss.id is null are ignored.
To solve the problem you should use root.join("boss", JoinType.LEFT) instead of root.get("boss")

how to align column names in Postgres Spring-data and liquibase?

I was trying to follow this tutorial to set up a simple spring boot app with postgres. They used Docker to set up the db, and liquibase. I have used spring-data and JPA before but not postgres or liquibase.
I was trying to set up a simple Student db. Here's my entity:
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotNull
private String firstName;
#NotNull
private String lastName;
public Student(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
Here's the changelog for liquibase
create table "student" (
id bigserial not null,
firstName varchar(50) not null,
lastName varchar(50) not null,
primary key (id)
);
I am loading a few students via a class implementing CommandLineRunner, just to have some initial data
Upon startup, I get the following error:
Caused by: org.postgresql.util.PSQLException: ERROR: column "first_name" of relation "student" does not exist
What is changing the column of firstName into first_name?
I could use #Column("first_name") in the Student entity but then that doesn't seem to line up with the table definition in the changeset.
I don't know why liquibase needs the table definition, with JPA there. I thought it was easier without it, by just letting JPA create the table from the #Entity.
Bigger picture is I'm just trying to do a proof of concept/scaffold for a quick spring boot app with postgres on the backend and ReactJS on the front end. This tutorial might be better for that, if I could get the postgres back end figured out. Don't really need docker, but I thought that would make it easier. But now that and liquibase seem to be creating a problem by persisting a db and table that don't match up.

How to retrieve #IndexedEmbedded properties?

FullTextQuery.setProjection("id", "author") ignored author's id, name property. How can I retrieve these properties?
#MappedSuperclass
class BaseContent{
#IndexedEmbedded(prefix = "author.", includePaths = {"id", "name"}) #ManyToOne
Author author;
}
#Entity #Indexed
class Content extends BaseContent{
#Id #DocumentId
Integer id;
}
#Entity
class Author{
#Id
Integer id;
#Field(store = Store.YES)
String name;
}
EDIT:
Is this query correct?.
FullTextQuery ftq = fullTextSession.createFullTextQuery(luceneQuery, Content.class);
ftq.setProjection("id", "author.id", "author.name");
ftq.setResultTransformer(new AliasToBeanResultTransformer(Content.class));
List<Content> result = ftq.list();
Use the author. prefix.
fullTextQuery.setProjection("author.id", "author.name")
EDIT: Did you try inspecting the results without your transformer? It should return a List<Object[]> with the content of the projections. If it does, then it's the transformer that isn't working. I very much doubt that AliasToBeanResultTransformer is able to handle composite aliases such as "author.name". You'll probably need you own transformer.
Also note that:
If you just want to get the Content entity, and getting it from the database doesn't bother you, just remove the projection and result transformer: Hibernate Search will get a List<Content> from the database for you.
If what you're trying to do is to avoid loading anything from the database, then you're on the right path. But as I said, you'll probably need a custom transformer.

renaming JPA Entities from Tables

I am reprograming an application.
The current database has table names like “User” which is a reserved word in the new db so I changed the table name to “NewUser”. I also had to change a few column names. I would like to code it so it imports the new name but changes them immediately in the app back to the reserved word so I don’t have to spend a lot of time re-programming:
Example Code:
#Entity
// NewUser is the new table name but still User below. I would like to keep the user
//as the class name but go after NewUser in the db
public class User implements java.io.Serializable {
private static final long serialVersionUID = -6091824661950209090L;
/** Primary key */
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
// uid is now newuid in the table but again I want to keep uid in the app
//but reference newuid from the db
protected int uid;
public int getUid() {
return this.uid;
}
public void setUid(int uid) {
this.uid = uid;
}
Just add #Table(name = "NewUser") to your entity. It will remap the entity to new table name, but keep User as entity name which is what is used in queries. You will only have to rewrite native queries if you have them, since that is pure SQL. Also, for renaming column names use #Column(name = "newuid").