I have an entity table, which does not associate to another two tables. Now I have a requirement to include also two columns, one from each of these tables, together with my entity in a response/dto.
As there is no association between the tables, I do not want to add the joins in my entity but just a special query. Is there a clean way to do so?
e.g.
#Entity
public class MyEntity {
#Id private Integer id;
#Column private String name;
#Column private String other;
}
public interface MyEntityRepo extends Repository<MyEntity, Integer> {
#Query("select a.info, b.desc, m.id, m.name, m.other from MyEntity m join TableA a on m.name = a.name join TableB b on m.name = b.name order by m.id")
List<MyEntityPlus> findAllPlus();
}
I tried using projection to create an interface MyEntityPlus like below, but it does not seem to work.
public interface MyEntityPlus {
String getInfo(); // from TableA
String getDesc(); // from TableB
Integer getId(); // from MyEntity
String getName(); // from MyEntity
String getOther(); // from MyEntity
}
Related
I have a one to one relationship like this:
#Entity
public class Modification {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="modification_id")
Long id;
#Column(name="second_line", length=1000)
String firstLine;
#OneToOne
#JoinColumn(name="tm_satus")
StatusOrder tmStatus;
// Constructors getters and setters
}
#Entity
public class StatusOrder {
#Id
#Column(name="status")
int status;
#Column(name="status_order")
int order;
// Constructors getters and setters
}
So every Modification has a StatusOrder.
Now I want to perform a query where I select from Modification table ordered by order field in StatusOrder.
Is there a way to have a method in my repository like:
Page<Modification> findAllOrderByStatusOrder(Pageable pageable);
Try these,
#Query("SELECT m FROM Modiciation m ORDER BY m.tmStatus.order DESC")
Page<Modification> findAllOrderByStatusOrder(Pageable pageable);
or (I don't know if this'll work.)
Page<Modification> findAllOrderBytmStatus_orderDesc(Pageable pageable);
I have search whole day but couldn't find the solution.
I have 2 entities Table A and Table B. Table A has 1 primary key and Table B has composite key. There is oneToMany mapping between Table A and Table B
I have created Table A Like below
#Entity
public class TableA {
#Id
private Integer sId;
private Integer roleNo;
private String studentName;
#OneToMany
#JoinColumn(name="roleNo")
private List<TableB> tableb;
}
Table B looks like below
#Entity
public class TableB {
#EmbededId
private CKStudent student;
private String activities;
}
here is my composite class
#Embeddable
public class CKStudent {
private Integer roleNo;
private Integer ActivityId;
}
I want to create query like this
select *
from tableA a
left join tableB on a.roleNo = b.roleNo
where a.sId = 1 and b.activityId = 3
I have written jpa method for it
List<TableA> findBySIdAndtablebStudentActivityId(Integer id,Integer activityId);
but I am not getting the required result,
I am getting result for all the activityId, and not the activityId which I am passing through parameter.
Any help would be appreciated.
UPDATE
This JPA is creating 2 queries
Query #1
select
tbla0_.sId as s_id1_8_, tbla0_.roleNo as rol2_8_,
tbla0_.studentName as student_name3_8
from tableA tbla0_
left outer join tableB tbl1_ on tbla0_.role_id = tbl1_.role_id
where tbla0_.sId = ?
and tbl1_.activityId = ?
which is the correct query
Query #2
select
tblB1_.roleNo as roleNo_21_0_, tblB1_.activityId as activityId2_21_0_,
tblB1_.activities as act1_21_1_,
from tableB tblB1_
where tblB1_.roleNo = ?
which is wrong and returning wrong results.
I have two (Hibernate-based) Spring Data JPA domain classes, the "One" side Customer.class:
#Entity
#Table(name = "sys_customer")
#Data
public class Customer implements Serializable {
#Id
#Column(name = "cust_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "cust_name")
private String customerName;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "customer")
private Set<Order> orders;
}
and the "Many" side Order.class:
#Entity
#Table(name = "sys_order")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Order implements Serializable {
#Id
#Column(name = "order_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "order_name")
private String orderName;
#ManyToOne
#JoinColumn(name = "order_cust_id", referencedColumnName = "cust_id")
private Customer customer;
public Order( String orderName) {
this.orderName = orderName;
}
public Order(String orderName, Customer customer) {
this.orderName = orderName;
this.customer = customer;
}
}
I have OrderRepository interface which extends JpaRepository interface and JpaSpecificationExecutor interface:
public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> {
}
I have a OrderSpecification.class with the static method searchByCustomerName:
public class OrderSpecification {
public static Specification<Order> searchByCustomerName(String customerName) {
return new Specification<Order>() {
#Override
public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
Join<Order, Customer> join = root.join("customer");
return criteriaBuilder.like(join.get("customerName"), "%" + customerName + "%");
//return criteriaBuilder.like(root.get("customer").get("customerName"), "%" + customerName + "%");
}
};
}
}
To find the differences between get("property") chain and Join, I wrote a simple test method and comment out the above OrderSpecificatin.class code
#Test
#Transactional
public void testFindOrderByCustomerName(){
String name = "adam";
List<Order> orders = orderRepository.findAll(OrderSpecification.searchByCustomerName(name));
for(Order order: orders){
Customer customer = order.getCustomer();
log.info(new StringBuilder().append(customer.getId()).append(" ").append(customer.getCustomerName()).toString());
}
}
I found that:
get("property") chain use a cross-join(which is very bad performancing) while Join use inner-join(since ManyToOne() by default is Fetch= FetchType.EAGER)
/* get("property") chain: Hibernate: select order0_.order_id as
order_id1_1_, order0_.order_cust_id as order_cu3_1_,
order0_.order_name as order_na2_1_ from sys_order order0_ cross join
sys_customer customer1_ where order0_.order_cust_id=customer1_.cust_id
and (customer1_.cust_name like ?) Hibernate: select customer0_.cust_id
as cust_id1_0_0_, customer0_.cust_name as cust_nam2_0_0_ from
sys_customer customer0_ where customer0_.cust_id=? */
/** * "Join": * Hibernate: select order0_.order_id as order_id1_1_,
order0_.order_cust_id as order_cu3_1_, order0_.order_name as
order_na2_1_ from sys_order order0_ inner join sys_customer customer1_
on order0_.order_cust_id=customer1_.cust_id where customer1_.cust_name
like ? * Hibernate: select customer0_.cust_id as cust_id1_0_0_,
customer0_.cust_name as cust_nam2_0_0_ from sys_customer customer0_
where customer0_.cust_id=? */
My questions are:
Can I specify the Join type(inner, all three outers) or Fetch Type(LAZY, EAGER) when using get("property") chain approach to avoid cross-join?
What scenario/best practice should I use get("chain") or always stay in Join?
Does the approach OrderSpecification.class with static method obey a good OOP design pattern?
You can't specify the join type for paths. It will use INNER join semantics by default and that is mandated by the JPA specification. If you want a different join type, you will have to create joins explicitly. The fact that using get renders as cross joins is a limitation of the old query model of Hibernate, but Hibernate 6.0 will fix this. The semantics are the same though and the query planner of your database should be able to treat both queries the same way. Maybe you just need to update your database version?
There is no "best practice" i.e. this really depends on your needs. Explicit joins are just that, explicit. So multiple calls to join will create multiple joins in SQL.
As for the OOP question, I think this is fine, yes.
There are two tables PersonEntity and cityentity. PersonEntity in the database is linked to cityentity by the external key fk_cityid. I need to select all the records (names) of the PersonEntity table with the given CityId. Join is used everywhere for this, but in this case I don't need data from the cityentity table, only the name field of the PersonEntity table. Here is a description of the classes:
#Entity
public class PersonEntity {
private Long id;
private String name;
private CityEntity cityId;
}
#Entity
public class CityEntity {
private Long id;
private String name;
}
Here is the HQL query:
#Repository
public interface PersonEntityRepository extends JpaRepository<PersonEntity, Long> {
#Query("select p.name FROM PersonEntity p where (p.name = :name or :name is null) " +
"and (p.cityId = :cityId or :cityId is null)")
List<PersonEntity> findByNameAndCity (
#Param("name") String name,
#Param("cityId") CityEntity cityId);
}
tried by id:
#Query("select p.name FROM PersonEntity p where (p.name = :name or :name is null) " +
"and (p.cityId.id = :cityId or :cityId is null)")
List<PersonEntity> findByNameAndCity (
#Param("name") String name,
#Param("cityId") Long cityId);
In both cases, the error is: "the data type could not be determined".
options for calling the function:
servisPerson.findByNameAndCity (null, cityId);
or
servisPerson.findByNameAndCity (name, null);
In fact, there are more than two parameters. I only show two for simplification.
servisPerson.findByNameAndCity (name, age, ..., cityId);
Person entity should look something like this
#Entity
public class PersonEntity {
private Long id;
private String name;
#OneToOne
#JoinColumn(name = "city_id")
private CityEntity city;
}
Than you can write your query like this
List<PersonEntity> findByNameAndCityOrNameIsNullOrCityIsNull(String name, CityEntity city);
Spring Data JPA is so smart to generate the query for you under the hood.
BTW, you attempted to write JPQL, not HQL.
EDIT (reaction on comment about long method names):
I would suggest to avoid creating JPQL query, because is is more error prone. If you don't like lengthy method name, you can wrap it into shorter default method within repository:
#Repository
public interface PersonEntityRepository extends JpaRepository<PersonEntity, Long> {
List<PersonEntity> findByNameAndCityOrNameIsNullOrCityIsNull(String name, CityEntity city);
default List<PersonEntity> shortName(String name, CityEntity city) {
return findByNameAndCityOrNameIsNullOrCityIsNull(name, city);
}
}
Two tables:
TABLE_1:
REC_ID
1
2
3
4
TABLE_2:
REC_ID REC_VAL
2 A
3 B
Entity classes (basic structure):
#Entity
#Table(name="TABLE_1")
public class Entity1 {
#Id
#Column(name="REC_ID")
private String recId;
//getters and setters
}
#Entity
#Table(name="TABLE_2")
public class Entity2 {
#Id
#Column(name="REC_ID")
private String recId;
#Column(name="REC_VAL")
private String recVal;
//getters and setters
}
SQL Query and result:
SELECT T1.REC_ID, T2.REC_VAL FROM TABLE_1 T1 LEFT OUTER JOIN TABLE_2 T2 ON T1.REC_ID = T2.RED_ID
Result:
REC_ID REC_VAL
1 null
2 A
3 B
4 null
JPQL Query:
SELECT e1.recId, e2.recVal FROM Entity1 e1 LEFT JOIN e1.<an Entity2 field in Entity1*>
* I know I don't have it in the given structure above, but I want to know how to do it right. And how do I choose from #ManyToOne, #OneToOne, etc.
How do I modify the Entity classes and the JPQL query to achieve the same result as the SQL query? I've been trying various things, nothing works. It wouldn't allow me to create two fields with the same column name, or to define the String as the #JoinColumn. I almost got it working, but the generated SQL query contains a reference to REC_ID_REC_ID column in TABLE_2 which doesn't exist. And after Googling so much, I can't find a proper guide for this (ignoring that JPQL does not support in-line join conditions!)
You need to have a OneToOne association between the entities. And the association, on the owning side, must be annotated with #MapsId. Here's an example taken from the Hibernate documentation, which maps to your use-case:
#Entity
public class Body {
#Id
public Long getId() { return id; }
#OneToOne(cascade = CascadeType.ALL)
#MapsId
public Heart getHeart() {
return heart;
}
...
}
#Entity
public class Heart {
#Id
public Long getId() { ...}
}
Once you have that, you can use a query such as
select b.foo, h.bar from Body b left join b.heart h where ...