I have the following entity class where objectId is alphanumeric string
#Data
#Entity
#Table(name = "CASE_INFO")
public class CaseInfo {
#Id
#Column(name = "ID")
private UUID id;
#Column(name = "OBJECT_ID")
private String objectId;
}
I also have the following repository
#Repository
public interface CaseInfoRepository extends JpaRepository<CaseInfo, UUID>, JpaSpecificationExecutor<CaseInfo> { }
Now when I make a query based on Specification and Pageable objects like this
Specification<CaseInfo> specification = (Specification<CaseInfo>) (root, query, criteriaBuilder) -> criteriaBuilder.and();
Pageable pageable = PageRequest.of(0, 25, Sort.by("objectId"));
caseInfoRepository.findAll(specification, pageable);
I want to be able to sort result based on CaseInfo.objectId numerically so that the order by in sql query would look something like this
... order by to_number(OBJECT_ID) ...
If I do something like
PageRequest.of(0, 25, Sort.by("objectId"))
it orders alphabetically so that "100" is before "2" which is wrong for my use case.
I found https://www.baeldung.com/spring-data-jpa-query where it uses
JpaSort.unsafe("LENGTH(name)")
but this does not seem to work because if I do
PageRequest.of(0, 25, JpaSort.unsafe("to_number(objectId)"))
It gives error message "No property to found for type CaseInfo! Did you mean 'id'?"
Does anybody know a way to give custom expression to Sort object?
You could add a formula field casting the string value to the numeric one and then sort by this field or use default implementation findAll(Pageable pageable):
#Data
#Entity
#Table(name = "CASE_INFO")
public class CaseInfo {
#Id
#Column(name = "ID")
private UUID id;
#Column(name = "OBJECT_ID")
private String objectId;
#Formula(value = "CAST(OBJECT_ID AS NUMERIC(10, 0))")
private int numObjectId;
}
Repository:
#Repository
public interface CaseInfoRepository extends
JpaRepository<CaseInfo, UUID>, JpaSpecificationExecutor<CaseInfo> {
// sort by formula field
List<CaseInfo> findAllByOrderByNumObjectId(Pageable pageable);
}
There is a trick with using CAST function in #Formula -- it is very likely to fail when casting to INT, INTEGER, BIGINT but it works when casting to NUMERIC(10, 0).
Numeric also should be large enough to contain the value within the string after cast otherwise an integer overflow may occur.
Related
I'm trying to use querydsl to build a query which joins two tables. However, a slight discrepancy between the corresponding Java class data types seems to prevent me from directly comparing the two columns values in my query. The data type within the object corresponding to table A is java.util.UUID while the data type of the object corresponding to table B is String.
I have something like the following:
#Table(name = "TABLE_A")
public class TableA {
#Column(name = "uuid")
#Type(type = "uuid-char")
private UUID uuid;
}
#Table(name = "TABLE_B")
public class TableB {
#Column(name = "uuid")
private String uuid;
}
#Service
public class QueryService {
private final JPQLQueryFactory queryFactory;
public UUID getData(UUID input) {
return queryFactory.select(QTableA.tableA.uuid)
.from(QTableA.tableA)
.innerJoin(QTableB.tableB)
.on(QTableB.tableB.uuid.eq(QTableA.tableA.uuid.toString()))
.where(QTableA.tableA.uuid.eq(input))
.fetchOne();
}
}
The above code does not return anything. However, the below code seems to work:
#Service
public class QueryService {
private final JPQLQueryFactory queryFactory;
public UUID getData(UUID input) {
return queryFactory.select(QTableA.tableA.uuid)
.from(QTableA.tableA)
.innerJoin(QTableB.tableB)
.on(QTableA.tableA.uuid.eq(input)
.and(QTableB.tableB.uuid.eq(input.toString()))
.where(QTableA.tableA.uuid.eq(input))
.fetchOne();
}
}
I don't understand why directly comparing the columns doesn't work, but comparing them to a common variable does work. Would QTableA.tableA.uuid.toString() not call the proper toString() method?
Please try:
.on(QTableB.tableB.uuid.toString().eq(QTableA.tableA.uuid.toString()))
Apply toString on QTableB.tableB.uuid
It's looking like a direct comparison is not possible with the current version. Querydsl's type checking is too strict for a direct comparison to be done on these kinds of differing data types.
First sorry this is long but I wanted to provide all information possible.
I am working on a much larger query that will build on this hence the reason I am not taking an easier or other approaches. In addition I can't really change the way we implemented the DB and Domain Objects.
My problem is I can't get a Spring Data JPA Query to work with an Enum. The field is an Enum in the DB as well. Here is the abbreviated code.
The SQL for the 2 tables:
CREATE TABLE my_order (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
creation_date TIMESTAMP NOT NULL,
);
CREATE TYPE ORDER_STATE as ENUM ('new', 'open', 'closed');
CREATE CAST (CHARACTER VARYING AS ORDER_STATE) WITH INOUT AS IMPLICIT;
CREATE TABLE my_order_history (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_date TIMESTAMP NOT NULL,
order_state ORDER_STATE NOT NULL,
order_id INT REFERENCES my_order
);
Here is the corresponding Domain Objects:
#Entity
#Table(name = "my_order")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "creation_date")
private Date creationDate;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<OrderHistory> orderHistories;
}
#Entity
#Table(name = "my_order_history")
public class OrderHistory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "order_date ")
private Date orderDate;
#Column(name = "order_state")
#Convert(converter = OrderStateConverter.class)
private OrderState orderState
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
Here is the converter:
#Component
#Converter(autoApply = true)
public class OrderStateConverter implement AttributeConverter<OrderState, String> {
#Override
public String convertToDatabaseColumn(OrderState attribute) {
return attribute.getValue;
}
#Override
public OrderState convertToEntityAttribute(String dbData) {
return OrderState.fromValue(dbData);
}
}
Here is the Enum:
public enum OrderState {
NEW("new"), OPEN("open"), CLOSED("closed");
#Getter
private String value;
private OrderState(String value) {
this.value = value;
}
public static OrderState fromValue(String value) {
for (OrderState orderState : values()) {
if (orderState.getValue().equals(value)) {
return orderState;
}
}
return null;
}
}
Here is my Spring Repo. I am putting the 3 ways I have tried and then below I will give you the exceptions I am receiving with each:
#Repository
public interface OrderRepository extends PagingAndSortingRepository<Order, Long> {
#Query("SELECT o FROM Order o JOIN o.orderHistories oh WHERE oh.orderState = :orderState")
List<Order> getOrdersByOrderState1(#Param("orderState") OrderState orderState);
#Query("SELECT o FROM Order o JOIN o.orderHistories oh WHERE oh.orderState = :orderState")
List<Order> getOrdersByOrderState2(#Param("orderState") String orderState);
#Query("SELECT o FROM Order o JOIN o.orderHistories oh WHERE oh.orderState = :#(#orderState?.getValue())")
List<Order> getOrdersByOrderState3(#Param("orderState") OrderState orderState);
}
For #1 when I provide an OrderState enum I get the following exception:
Caused by: org.postgres.util.PSQLException: ERROR: operator does not exist: order_state = character varying
Hint: No operator matches the given name and argument types. You might need explicit type casts.
For #2 when I provide OrderState.getValue(), which is a String, I get the following exception:
java.jang.IllegalArgumentException: Parameter value [new] did not match expected type [com.testing.enums.OrderState (n/a)]
For #3 when I provide an OrderState enum I get the following exception (same as #2):
java.jang.IllegalArgumentException: Parameter value [new] did not match expected type [com.testing.enums.OrderState (n/a)]
Basically I try to send in the enum and get an error but I also get an error when I try to send in a String. Is there anything I can do? What is exactly happening?
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!
I want to ask about what is the most efficient way to search about specific data from a database without doing a for loop in all of the records?
I have a project on java spring and I have this Entity:
#Entity
#Table(name = "USERS") public class USERS {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "UID")
private Integer id;
#Column(name = "FName")
private String firstName;
#Column(name = "SName")
private String secondName;
#Column(name = "TName")
private String thirdName;
#Column(name = "LName")
private String fourthName;
#Column(name = "Email")
private String email;
#Column(name = "PW")
private String password;
#Column(name = "MNumber")
private String mobileNumber;
#Column(name = "ISDeleted")
private boolean isUserDeleted;
//---------------------- Getters and Setters ----------------------
and I made this service:
public List<USERS> findAllActive() {
List<USERS> usersList = new ArrayList<USERS>();
for (USERS users: usersRepository.findAll()){
if (!users.isUserDeleted()){
usersList.add(users);
}
}
return usersList;
}
For example; I have one property for User, if he is active or not.
So, my question; what is the most efficient way to do get specific data like retrieving all of the active users from the DB without doing a for loop like in the code above? Because if the list of users is a 1 Million or more, it could have performance issues.
Assuming that you are using JpaRepository then you can create custom query.
#Query("SELECT u FROM USERS u WHERE u.userDeleted = false")
List<USERS> findNotDeletedUsers();
and then call usersRepository.findNotDeletedUsers();
First of all, use an index on the field you want to search on (this won't help you much if the column has only two distinct values, but will make a huge difference if the value has high sparsity).
#Entity
#Table(name = "USERS",
indexes = {
// not a huge performance gain, since the column values are true/false
#Index(name = "index_by_active", columnList="ISDeleted", unique = false),
// possible huge performance gain, since only the relevant records are scanned
#Index(name = "index_by_first_name", columnList="FName", unique = false)})
public class USERS {...}
Then, define a query method that uses the indexed field (if you are using spring data it would look as follows).
public interface UsersRepository extends CrudRepository<USERS, Long> {
List<USERS> findUsersByISDeleted(boolean deleted);
List<USERS> findUsersByFName(String name);
List<USERS> findUsersByFNameAndISDeleted(String name, boolean deleted);
}
Queries on indexed fields will leverage the underlying index and provide an efficient access plan (so you won't end up scanning the whole table in order to extract a subset of entities matching a given criteria).
The solution from #Madis is okay. But if you always want to get users which are not deleted in all queries, you can specify it on Entity:
#Entity
#Table(name = "USERS")
#Where("ISDeleted = false")
public class USERS {
So now the condition "ISDeleted = false" is automatically append to all queries from the UserRepository. You can use usersRepository.findAll() instead of.
You don't need to specify any sql query or where clause. CrudRepository will do it for you automatically. Just use below code and pass true/false on need basis
List<Users> findIsUserDeleted(boolean isDeleted)
I'm using Spring JPA and I need to have a native query. With that query, I need to get only two fields from the table, so I'm trying to use Projections. It isn't working, this is the error I'm getting:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [com.example.IdsOnly]
I tried to follow precisely the instructions of that page I linked, I tried to make my query non-native (do I actually need it to be native if I use projections, btw?), but I always get that error.
If I use an interface it works, but the results are proxies and I really need them to be "normal results" that I can turn into json.
So, here's my code. The Entity:
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#Entity
#Table(name = "TestTable")
public class TestTable {
#Id
#Basic(optional = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "Id")
private Integer id;
#Column(name = "OtherId")
private String otherId;
#Column(name = "CreationDate")
#Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
#Column(name = "Type")
private Integer type;
}
The class for the projection:
import lombok.Value;
#Value // This annotation fills in the "hashCode" and "equals" methods, plus the all-arguments constructor
public class IdsOnly {
private final Integer id;
private final String otherId;
}
The Repository:
public interface TestTableRepository extends JpaRepository<TestTable, Integer> {
#Query(value = "select Id, OtherId from TestTable where CreationDate > ?1 and Type in (?2)", nativeQuery = true)
public Collection<IdsOnly> findEntriesAfterDate(Date creationDate, List<Integer> types);
}
And the code that tries to get the data:
#Autowired
TestTableRepository ttRepo;
...
Date theDate = ...
List<Integer> theListOfTypes = ...
...
Collection<IdsOnly> results = ttRepo.findEntriesAfterDate(theDate, theListOfTypes);
Thanks for the help. I really don't understand what I'm doing wrong.
with spring data you can cut the middle-man and simply define
public interface IdsOnly {
Integer getId();
String getOtherId();
}
and use a native query like;
#Query(value = "Id, OtherId from TestTable where CreationDate > ?1 and Type in (?2)", nativeQuery = true)
public Collection<IdsOnly> findEntriesAfterDate(Date creationDate, List<Integer> types);
check out https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
The query should be using a constructor expression:
#Query("select new com.example.IdsOnly(t.id, t.otherId) from TestTable t where t.creationDate > ?1 and t.type in (?2)")
And i dont know Lombok, but make sure there is a constructor that takes the two IDs as parameters.
JPA 2.1 introduces an interesting ConstructorResult feature if you want to keep it native.
You can return list of Object Array (List) as return type of the native query method in repository class.
#Query(
value = "SELECT [type],sum([cost]),[currency] FROM [CostDetails] " +
"where product_id = ? group by [type],[currency] ",
nativeQuery = true
)
public List<Object[]> getCostDetailsByProduct(Long productId);
for(Object[] obj : objectList){
String type = (String) obj[0];
Double cost = (Double) obj[1];
String currency = (String) obj[2];
}
#Query(value = "select isler.saat_dilimi as SAAT, isler.deger as DEGER from isler where isler.id=:id", nativeQuery = true)
List<Period> getById(#Param("id") Long id);
public interface Period{
Long getDEGER();
Long getSAAT();
}
as seen in the example code for native query given above, cast return values to any value like as "SAAT", "DEGER" and then define interface "period" which have getDEGER() and getSAAT(). Even if I have not understand why parameter after get must be uppercase, in lowercase scenario it didn't work properly. ie. interface with getDeger(), getSaat() does not work properly in my case.