I am trying to do a #Query in JPA that does not return a Entity from my Model.
In my Model I have BudgetPlan, BudgetGeschaftsfeld, BudgetMarke, BudgetKampagne, BudgetBucket, Kosten Entities which have a #OneToMany relationship.
So Budgetplan has many BudgetGeschaftsfeld which has many BudgetMarke and so on.
I want to return a CustomKosten with all couple of the standard Kosten attributes, and the ID to each "Parent ID". A Pojo of the Class would look like this.
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
public class CustomKosten {
private long id;
private String name;
private double betrag;
private String status;
private Date local_date;
private long idBucket;
private String bucket;
private long idKampagne;
private String kampagne;
private long idMarke;
private String marke;
private long idGeschaeftsfeld;
private String geschaeftsfeld;
private long idPlan;
private String plan;
}
I have accomplished the SQL query in MySQL and it gets the result I want. Which looks like this.
public interface CustomKostenRepository extends JpaRepository<CustomKosten, Long> {
#Query(nativeQuery = true, value="select id, name, betrag, status, local_date, idbucket, bucket, idkampagne, kampagne, idmarke, marke, idgeschaeftsfeld, geschaeftsfeld, idplan, plan from \r\n"
+ "(select * from (select * from (select * from (select * from (select pid as pidgeschaeftsfeld, id as idgeschaeftsfeld, name as geschaeftsfeld from budman_db.budget where dtype=\"Budgetgeschaftsfeld\") as tgeschaeftsfeld \r\n"
+ "left join (select id as idplan, name as plan from budman_db.budget where dtype=\"Budgetplan\" ) as tplan on tgeschaeftsfeld.pidgeschaeftsfeld= tplan.idplan) as t1\r\n"
+ "left join (select pid as pidmarke, id as idmarke, name as marke from budman_db.budget where dtype=\"Budgetmarke\") as tmarke on t1.idgeschaeftsfeld= tmarke.pidmarke) as t2\r\n"
+ "Left join (select pid as pidkampagne, id as idkampagne, name as kampagne from budman_db.budget where dtype=\"Budgetkampagne\") as tkampagne on t2.idmarke= tkampagne.pidkampagne ) as t3\r\n"
+ "left join (select pid as pidbucket, id as idbucket, name as bucket from budman_db.budget where dtype=\"Budgetbucket\") as tbucket on t3.idkampagne= tbucket.pidbucket) as t4\r\n"
+ "left join (select * from budman_db.kosten ) as tkosten on t4.idbucket= tkosten.pid")
public List<CustomKosten> getCustomKostenKomplex();
}
is the result I'm trying to get even possible?
I am getting this Error when running my code.
Any advice is much appreciated
Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.bm.ent.kosten.CustomKosten
Edit:
After following the Example Eugene adviced im getting another error.
Caused by: java.lang.IllegalArgumentException: Could not locate appropriate
constructor on class : com.bm.ent.kosten.CustomKosten
I'm guessing it has something to do with the Type of each field. And I think I don't have to put a type on those which have a primitive class.
note: I purposely left out the date to test. Any advice?
#Entity(name= "Kosten")
#Table(name= "kosten")
#NamedNativeQuery(name = "CustomKostenAll",
query= "select id, name, betrag, status, idbucket, bucket, idkampagne, kampagne, idmarke, marke, idgeschaeftsfeld, geschaeftsfeld, idplan, plan from \r\n"
+ "(select * from (select * from (select * from (select * from (select pid as pidgeschaeftsfeld, id as idgeschaeftsfeld, name as geschaeftsfeld from budman_db.budget where dtype=\"Budgetgeschaftsfeld\") as tgeschaeftsfeld \r\n"
+ "left join (select id as idplan, name as plan from budman_db.budget where dtype=\"Budgetplan\" ) as tplan on tgeschaeftsfeld.pidgeschaeftsfeld= tplan.idplan) as t1\r\n"
+ "left join (select pid as pidmarke, id as idmarke, name as marke from budman_db.budget where dtype=\"Budgetmarke\") as tmarke on t1.idgeschaeftsfeld= tmarke.pidmarke) as t2\r\n"
+ "left join (select pid as pidkampagne, id as idkampagne, name as kampagne from budman_db.budget where dtype=\"Budgetkampagne\") as tkampagne on t2.idmarke= tkampagne.pidkampagne ) as t3\r\n"
+ "left join (select pid as pidbucket, id as idbucket, name as bucket from budman_db.budget where dtype=\"Budgetbucket\") as tbucket on t3.idkampagne= tbucket.pidbucket) as t4\r\n"
+ "left join (select * from budman_db.kosten ) as tkosten on t4.idbucket= tkosten.pid",
resultSetMapping= "CustomKostenMapping")
#SqlResultSetMapping(name = "CustomKostenMapping",
classes = {
#ConstructorResult(targetClass = CustomKosten.class,
columns = {
#ColumnResult(name = "id"),
#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "betrag"),
#ColumnResult(name = "status", type = Status.class),
// #ColumnResult(name = "local_date"),
#ColumnResult(name = "idbucket"),
#ColumnResult(name = "bucket", type = String.class),
#ColumnResult(name = "idkampagne"),
#ColumnResult(name = "kampagne", type = String.class),
#ColumnResult(name = "idmarke"),
#ColumnResult(name = "marke", type = String.class),
#ColumnResult(name = "idgeschaeftsfeld"),
#ColumnResult(name = "geschaeftsfeld", type = String.class),
#ColumnResult(name = "idplan"),
#ColumnResult(name = "plan", type = String.class),
}
)
})
#Getter
#Setter
#NoArgsConstructor
public class Kosten {...}
First, if you want to use the CustomKosten class as return type of your repository, you need to place annotate it with #Entity so that it becomes a managed type for JPA / Hibernate.
This however would only work if you have a table with a structure corresponding to your entity. In your case, you query several columns from different tables. Therefore, you need to specify how Hibernate should map all the returned columns to your Java class. I recommend this tutorial on SQL result set mapping by one of the Hibernate maintainers Thorben Janssen. If you already use Hibernate 6, you could also use a TupleTransformer as shown here.
account entity code:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
#NotFound(action = NotFoundAction.IGNORE)
private User user;
user entity code:
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Account> account = new ArrayList<>();
the repository cusotom method query annotion
#Query(value = "FROM Account a WHERE (:userId IS NULL Or a.user.id = :userId))
It generates this query:
from account account0_ cross join user user1_ where account0_.user_id=user1_.id and (? is null or user1_.id=?)
It worked fine before I updating springboot from 2.1.4.release to 2.6.5
which is expected to be the following:
from account account0_ WHERE (:userId IS NULL Or account0_.user_id = :userId)
Don't know if it's due to the version change or not.
Can someone give some clues?
It's a stange behavior on JPA if you use
#NotFound(action = NotFoundAction.IGNORE)
this above annotion,
remove this will behave normally.
I am trying to get the max value of a column in a table using a native query with the #Query annotation
I tried to derive it from the examples here: https://www.baeldung.com/spring-data-jpa-query
#Query(value = "SELECT max(i.sequence) " +
"FROM invoices as i " +
"WHERE i.fleet_id = ?1", nativeQuery = true)
Long findMaxSequence(String fleetId);
i ve also tried:
#Query(value = "SELECT max(i.sequence) " +
"FROM invoices as i " +
"WHERE i.fleet_id = :fleetId", nativeQuery = true)
Long findMaxSequence(#Param("fleetId") String fleetId);
When i call my method as :
long maxSeq = invoiceRepository.findMaxSequenceForFleetId(invoice.getFleetId());
I get a NullPointerException. Any ideas why?
Invoice entity looks like this :
#Entity
#Table(name = "invoices"}
public class Invoice implements Serializable {
#Id
private String id;
#Column
private long sequence;
#Column(length = 12)
private String fleetId;
// ...
}
The issue was due to the fact that the database was empty so the query was returning null
and basic types such as long cannot be assigned to null values. Weirdly the compiler did not complain..
I modified my code as below:
Long maxSeq = invoiceRepository.findMaxSequenceForFleetId(invoice.getFleetId());
if(maxSeq == null){
maxSeq = 0L;
}
I know the problem with N+1 select is well-known, but trying using different fetching strategies does not help to avoid it when I use native query.
I have 2 entities: A and B that correspond to the mysql tables A and B.
Several rows in table A could have the same b_id, that's why I use #ManyToOne annotation.
#Entity
#Table(name = "A")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Fetch(FetchMode.JOIN)
private B b;
}
I also create entity B.
#Entity
#Table(name = "B")
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String url;
}
I need to find all rows in A that are linked only for 1 row in table B. So I create native query in my ARepository
public interface ARepository extends JpaRepository<A, Integer> {
#Query(value = "SELECT a.id, a.b_id, a.name, b.url "
+ "FROM a "
+ "JOIN b ON a.b_id = b.id "
+ "GROUP BY a.b_id "
+ "HAVING count(a.b_id) = 1, nativeQuery = true)
List<A> getLinkedOnce();
But when when I run my restcontroller that just call getLinkedOnce method from repository, I see that in console, 1-st my query is called, then there are number of selects for each row in A that are end with
from B b0_ where b0_.id=?
I try to use different approaches, LAZY, EAGER and it does not work. I think because there is a usage of native query. But maybe the reason is another.
I have 2 object entities (User and Phone) and they are supposed to have many-to-many relations.
User.java
//all columns
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JoinTable(name = "USER_PHONE",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "phone_id", referencedColumnName = "id"))
private List<Phone> phones;
Phone.java
//all columns
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JoinTable(name = "USER_PHONE",
joinColumns = #JoinColumn(name = "phone_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"))
private List<User> userList;
Now, I add 2 users with IDs 1 and 2 in my USER table.
Then, I add a single phone with id 1 and map them to both the user IDs(1&2) .
My USER_PHONE table looks as below:
Select * from USER_PHONE;
+----------+---------+
| phone_id | user_id |
+----------+---------+
| 1 | 1 |
| 1 | 2 |
+----------+---------+
Now, I wish to remove a user with ID 2.
When I try to do this, I get an error
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`dbname`.`USER_PHONE`, CONSTRAINT `FKC6A847DAFA96A429` FOREIGN KEY (`user_id`) REFERENCES `USER` (`ID`))
My delete script:
String query = "DELETE User where id=?1";
try{
Query q = entityManager.createQuery(query);
q.setParameter(1,id);
q.executeUpdate();
System.out.println(System.currentTimeMillis() + " DELETE: userId " + id + " ==> deleted");
} catch(Exception e){
e.printStackTrace();
return false;
}
Any idea where am I going wrong ?
Thanks a lot :)
Try using entityManager.createNativeQuery(). You cannot use createQuery() because the table should be present as an entity in your Java code. Also, you need to use the exact SQL format.
String query = "DELETE FROM USER_PHONE WHERE user_id=?1";
try{
Query q = entityManager.createNativeQuery(query);
q.setParameter(1,id);
q.executeUpdate();
System.out.println(System.currentTimeMillis() + " DELETE User_Phone: userId " + id + " ==> deleted");
} catch(Exception e){
e.printStackTrace();
return false;
}`
First delete the row from USER_PHONE (using createNativeQuery()), and then from User (using createQuery())
Make the following change.
//User class
#ManyToMany(cascade = {CascadeType.MERGE,CascadeType.REMOVE}, fetch = FetchType.EAGER)
...
private List<Phone> phones;