Merge multiple Spring JPA Page objects results - java

My initial requirement was to fetch Car details as:
List<String> carMake = getUserCarMake();
Page<Car> carDetails = carRepository.findAllCarsInfo(carMake, pageRequest);
CarRepository.java
#Query(value="SELECT A FROM Cars A WHERE A.model = ?1
public Page<Cars> findAllCarsInfo(List<String> carMake, Pageable pageRequest);
Now my requirement has changed to fetch car details based car models for each car make. So I have changed the code as shown
for (Cars car : userCars) {
String carMake = car.getCarMake();
List<String> carModelForMake = new ArrayList<>();
List <CarModels> carModelList = car.getCarModels();
for (CarModels carModel : carModelList) {
carModelForMake.add(carModel.getModelName());
Page<Car> carDetails = carRepository.findAllCarsInfo(carModelForMake, carMake, pageRequest)
}
}
CarRepository.java
#Query(value="SELECT A FROM Cars A WHERE A.model IN ?1 AND A.make = ?2”
public Page<Car> findAllCarsInfo(List<String> carModel, String carMake,Pageable pageRequest);
So for each car i have a carMake and corresponding carModels for that make which i then pass to the query to fetch carDetails which is a Page Object. As a result same query is called multiple times for different carMake.
The problem is how do I manage the Page object here. In the above scenario the Page object will contain only the details of last car from the carModelList, rest will be overwritten as I do not
have an option of carDetails.addAll() as in case of List.
Note: I cannot use the below query as the model can overlap across different makes.
SELECT A FROM Cars A WHERE A.model IN ?1 AND A.make IN ?2
Also my pageRequest has size as (0, 20 )
I have tried to modify the query to remove pageRequest and use findAll to fetch the results in List and then convert them to PageObject but that breaks the pagination because the page.getContent() has the entire result set and not just 20 records.
Page<Car> carDetails = new PageImpl <>(carDetailsList, pageRequest, carDetailsList.size());
How can I effectively get Page object or merge different page objects here so that my pagination works as it did in my previous requirement.

Sometimes it is a good idea to create a special "query entity" class that includes everything that is needed to respond to a certain kind of client request.
The general idea is like this:
Let's say you'd have two classes in you domain:
#Entity
#Table(name = "table_a")
public class A {
#Id int id;
String propertyA;
int bId;
}
#Entity
public class B {
#Id int id;
String propertyB;
}
And then you'd combine the two two the mentioned "query entity" (outside of the domain).
#Entity
#Table(name = "table_a")
public class QueryEntity {
private #Id private int aId;
private String propertyA;
private B b;
public String propertyA() {
return propertyA;
}
public String propertyB() {
return b.propertyB;
}
}
I'm not quite sure whether this approach is applicable in your case, but hopefully it makes the idea clear.

Related

Android Room Relationship duplicating information

Having the weirdest issue here, all is working fine, except that my 1-to-M query is duplicating the data.
Customer table
#Entity(tableName = "customer_table")
public class Customer {
#ColumnInfo(name = "Customer_Serial", index = true)
#PrimaryKey
private int customerSerial;
#ColumnInfo(name = "Customer_Name")
private String customerName;
public Customer(int customerSerial, String customerName) {
this.customerSerial = customerSerial;
this.customerName = customerName;
}
}
Invoice table
#Entity(tableName = "invoice_table")
public class Invoice {
#ColumnInfo(name = "Invoice_Number", index = true)
#PrimaryKey
private int invoiceNumber;
#ColumnInfo(name = "Customer_Serial")
private int customerSerial;
public Invoice(int invoiceNumber, int customerSerial) {
this.invoiceNumber = invoiceNumber;
this.customerSerial = customerSerial;
}
}
CustomerInvoice relation
public class CustomerInvoice {
#Embedded public Customer customer;
#Relation(
parentColumn = "Customer_Serial",
entityColumn = "Customer_Serial"
)
public List<Invoice> invoices;
}
DAO
#Transaction
#Query("SELECT * FROM customer_table INNER JOIN invoice_table ON invoice_table.Customer_Serial = customer_table.Customer_Serial")
List<CustomerInvoice> getAllCustInvoices();
#Insert
void insertInvoice(Invoice... invoice);
#Insert
void insertCustomer(Customer... customer);
If I debug my application, set a breakpoint to test the Room stuff, then use the 'Evaluate' feature in Android Studio, I do the following
Invoice invoice1 = new Invoice(1234, 1);
Invoice invoice2 = new Invoice(2468, 1);
Customer customer = new Customer(1, "Test Customer");
dao.insertCustomer(customer);
dao.insertInvoice(invoice1);
dao.insertInvoice(invoice2);
If I then retrieve the information using getAllCustInvoices()
The list returned has 2 in it.
It has the customer duplicated for each invoice assigned to them, and then both invoices listed in each 1.
I'm not entirely sure where I am going wrong here, this is a simplified example of what the app itself is actually doing, simplified enough to see if something else in my code was causing the problem or not.
Turns out, even with the simplified example, it has the issue.
The issue
When #Relation is used Room extracts the children (ALL children) per parent (effectively running a second query to extract the children, hence the recommendation for using #Transaction ). By specifying the JOIN you are extracting the same parent for each child (i.e. the cartesian product) and hence the duplication.
i.e. Room does the equivalent of the JOIN internally
The Fix
#Transaction
#Query("SELECT * FROM customer_table")
List<CustomerInvoice> getAllCustInvoices();

How to sort a list of entities on the property from a collection of entities

I have an entity Mission which has a collection of entities Trip, and I would like to be able to sort the mission in function of the content in the collection of Trip.
The Trip entity:
#Entity
public Class Trip {
#Id private Long id;
#Column private LocalDateTime time;
}
The Mission entity:
#Entity
public Class Mission {
#Id private Long id;
#Column private String city;
#OneToMany(mappedBy = "mission")
private Collection<Trip> trips;
}
The Mission Repository:
#Repository
public interface MissionRepository extends PagingAndSortingRepository<Mission, Long>, QuerydslPredicateExecutor<Mission>, QuerydslBinderCustomizer<QMission> {
#Override
default void customize(final QuerydslBindings bindings, final QMission mission) {
bindings.bind(String.class).first((final StringPath path, final String value) -> path.containsIgnoreCase(value));
bindings.including(mission.city);
bindings.excludeUnlistedProperties(true);
}
Page<T> findAll(Predicate predicate, Pageable pageable);
}
The Mission Service:
#Service
#Transactional
public class MissionService {
private final MissionRepository missionRepository;
#Autowired
public MissionService(final MissionRepository missionRepository) {
this.missionRepository = missionRepository;
}
public Page<Mission> findAll(final Predicate predicate, final Pageable pageable) {
return this.missionRepository.findAll(predicate, pageable);
}
}
The Mission Controller:
#RestController
#RequestMapping("/api/missions")
public class MissionController {
private final MissionService missionService;
#Autowired
public MissionController(final MissionService missionService) {
this.missionService = missionService;
}
#GetMapping
#ResponseBody
public Page<Mission> getAllMissions(#QuerydslPredicate(root = Mission.class) final Predicate predicate, #PageableDefault final Pageable pageable) {
return this.missionService.findAll(predicate, pageable);
}
}
When calling /api/missions?sort=city,DESC, it work perfectly.
But when calling /api/missions?sort=trips.time,DESC I am getting duplicate entries which match the number of Trips linked with a Mission.
All these mission are correctly sorted, but the duplicate part is just a no go…
The objective is to be able to sort the Mission (ASC or DESC) in function of the departure date from the Trip collection (which would be the first Trip sorted in ASC).
So, how to sort this list of Mission on the content from the collection of Trip without getting the duplication part?
I would also like to avoid changing the REST method signature or the database structure, if possible.
I was thinking of adding a DISTINCT on the list, but It wouldn't work as the sorting need to be on the first Trip's time field. Sorting in ASC mode would work. But in DESC, it would then be sorted in function of the arrival date, and not the departure…
A crazy idea: would it be possible to automatically fill a transient property in the mission with the content of a query, and sort on it using the Pageable class?
1 – First option
I was able to fix my problem by using the #Formula annotation from Hibernate.
1.1 – Adding the following property inside the Mission entity
#Formula("(select min(t.time) from Trip t where t.mission_id = id)")
private LocalDateTime startDate;
1.2 – Sorting on the property
We just need to call the property.
/api/missions?sort=startDate,DESC.
2 – Second option
Currently I don't have any performance issue.
Yet, just in case, I am also considering to use a view which will then be linked to my entity.
NB: This one wasn't test, but is based on my understanding from the following blog.
2.1 – Create the view based on tables Trip and Mission
CREATE OR REPLACE VIEW mission_derived AS
select
m.`id` as id,
min(t.`time`) as startDate
from `trip` t
inner join `mission` m on t.`mission_id` = m.`id`
group by m.`id`;
2.2 – Create the java entity matching this view
#Entity
#Immutable
public class MissionDerived {
#Id
private long id;
#Column
private Date startDate;
}
2.3 – Adding the property to my Mission entity
#OneToOne
#PrimaryKeyJoinColumn(name="ID")
private MissionDerived derived;
2.4 – Sorting using this property
We would then need to call the property from the mission, and then the property from the derived entity.
/api/missions?sort=derived.startDate,DESC

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.

ORMLite - OneToOne relation

I got next database structure with OneToOne relation:
[company]
company_id (PK)
company_name
[company_configuration]
company_configuration_id (Autoincrement, PK)
company_id (UNIQUE KEY,FK)
company_configuration_v
I have been using ORMlite and I have next classes for this two tables:
#DatabaseTable(tableName = "company")
public class Company {
public static final String ID_COMPANY = "company_id";
public static final String COMPANY_NAME = "company_name";
#DatabaseField(generatedId = true, columnName = ID_COMPANY)
private int idCompany;
#DatabaseField(columnName = COMPANY_NAME)
private String companyName;
#DatabaseTable(tableName = "company_configuration")
public class CompanyConfiguration {
public static final String COMPANY_CONFIGURATION_ID = "company_configuration_id";
public static final String COMPANY_ID = "company_id";
public static final String COMPANY_CONFIGURATION_V = "company_configuration_v";
#DatabaseField(generatedId = true, columnName = COMPANY_CONFIGURATION_ID)
private int idCompanyConfiguration;
#DatabaseField(foreign = true,foreignAutoRefresh = true, columnName = COMPANY_ID)
private Company companyId;
#DatabaseField(columnName = COMPANY_CONFIGURATION_V)
private String companyConfigurationV;
Here is OneToOne relation because I want to divide a table with many columns.
As you can see in the example above, there is not relation from Company class to CompanyConfiguration class.
I know that I can add this snippet of code(examle below) into Company class, but I don't need a #ForeignCollectionField becaues the collection will contain only one CompanyConfiguration object:
#ForeignCollectionField()
private ForeignCollection<CompanyConfiguration> companyConfigurations;
I need to add something like this (examle below) into Company class and will get the reference from Company class to CompanyConfiguration class:
#OneToOne(targetEntity = CompanyDbConfig.class)
#JoinTable(name = "company_configuration")
#JoinColumn(name = "id_company")
CompanyConfiguration companyConfiguration;
Shortly, I want to get Company object using ORMlite. See the example below. After fetching company from the database, I want to have and CompanyConfiguration object within company object.
Company company = daoCompany.queryForId(id); //daoCompany is an instance of ORMlite Dao class
Is it possible and how to do that using ORMlite?
I posted an OrmLite question myself so I looked through the unanswered questions to see if there was anything I could answer. Even though this is an old topic, I wanted to take a stab at it in case it could help someone.
I've read your post a few times and I think you're asking how to load the information from two tables into one model. You're separating a rather large table into two in the database but you want it to come back as one model. If that is correct, here's my take on the code. This assumes you want to use objects to build the query instead of passing in a query string.
public class CompanyResult
{
public long CompanyId { get; set; }
public long ConfigurationId { get; set; }
public string Name { get; set; }
public string ConfigurationV { get; set; }
}
var query = _db.From<Company>
.Join<CompanyConfiguration>((c, cc) => c.idCompany == cc.idCompany)
.Where(c => c.idCompany == companyId)
.Select<CompanyConfiguration>((c, cc) = new {
CompanyId = c.idCompany,
ConfigurationId = cc.idCompanyConfiguration,
Name = c.companyName,
ConfigurationV - cc.companyConfigurationV
});
var results = _db.Single<CompanyResult>(query);
You'd keep your existing models so they could be used as DTOs. You'd just be using the new model model above to pass back the exact properties you want.
*I wrote this in Notepad++, forgive any typos.

Pass a object as query parameter and exclude the ID column

I have the following:
#Entity
#NamedQuery(name = "listCarsBySecurity", query = "SELECT c FROM Car c WHERE c.security = :security"
public class Car {
#Id
#GeneratedValue
private Long id;
#NotNull()
#Column(nullable = false)
private String make;
#NotNull()
#Column(nullable = false)
private String model;
// Some more fields
#NotNull()
#OneToOne (fetch = FetchType.LAZY, orphanRemoval=true)
private Security security = new Security();
// Some getters and setters
As you can see, the Car class has a "Security" object which is LAZY fetched. The security class looks like:
#Entity
public class Security {
#Id #GeneratedValue
private Long id;
// Security equipment. Add in alphanumerical order
private boolean abs;
private boolean airbag;
private boolean antispin;
// Some getters and setters
as you can see, the named query list try to list all cars which has a security entity equal to a provided security object.
The persistence method looks like:
#Stateless
public class CarEJB {
#PersistenceContext(unitName = "carcmsPU")
private EntityManager em;
public List<Car> listCarsBySecurity(Security security) {
TypedQuery<Car> query = em.createNamedQuery("listCarsBySecurity", Car.class);
query.setParameter("security", security);
return query.getResultList();
}
And a junit test looks like:
#Test
public void searchCar() throws Exception {
// Looks up the EJBs
carEJB = (CarEJB) ctx.lookup("java:global/classes/CarEJB");
// Create a new Ferrari with security = ABS brakes and Airbag
Car car = new Car();
car.setMake("Ferrari");
car.setModel("Some model");
car.setSubModel("Some sub model");
car.setEngine("Some engine");
car.setYear(1999);
car.getFuel().setGasoline(true);
car.setGearbox(Gearbox.AUTOMATIC);
car.setKilometres(323);
car.setDescription("This is a description");
Security security = new Security();
security.setAbs(true);
security.setAirbag(true);
car.setSecurity(security);
carEJB.createCar(car); // Persist
// Create a new security object and match it to the former one
Security securityObject = new Security();
securityObject.setAbs(true);
securityObject.setAirbag(true);
List<Car> carList = carEJB.listCarsBySecurity(securityObject);
assertTrue("Should contain at least 1 car with ABS and Airbag", carList.size() > 0 );
for (Car carTemporary : carList) {
System.out.println(carTemporary.toString());
}
}
The thing is that the list does not contain any cars at all. And I think I know why; the named query does try to match the security_id with NULL (since I have not define it).
My question is: How can I perform a query by passing a object as a query parameter with no ID and by not specify all fields which shall be compared inside that object? (or how exclude the ID from a search)?
Best regards
You can define a named query using OR and passing each one of the object's attributes. You can also use Criteria API to build a query based on the fields you want to query about. Since you already have a named query I'll leave that one to you.
If you decide to go that way (tough field by field comparation is kind of insane if your entity has way too many attributes). Using criteria you can do something like this:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> query = builder.createQuery(Car.class);
Root<Car> queryRoot = query.from(Car.class);
query.select(queryRoot);
Path<String> pathToYourField = root.get(yourField); //yourField is a variable containing the field.
//You can store all the variables in a list, iterate
//over them and do this for each one.
query.where(builder.and(builder.equal(pathToYourField, "particularValue"))); //You compare the path against a value.
//Rest of the fields / paths
TypedQuery<Car> typedQuery = entityManager.createQuery(query);
List<Car> cars = typedQuery.getResultList();
EDIT: About performance, check this links:
JPA Criteria vs NamedQueries
Another answer regarding Criteria vs HQL
Criteria overhead discussion

Categories