I am trying to do a bulk delete.
Query queryDeleteEntries = entityManager.createQuery("DELETE from
Entry r where (r.person.emailAddress like :name) and (r.otherData is null)");
int deletedEntriesCount = queryDeleteEntries.setParameter("name",
"tester." + emailAddressSuffix).executeUpdate();
I am getting an error:
Caused by: java.sql.SQLException: ORA-00933: SQL command not properly ended
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112) ~[DatabaseError.class:Oracle JDBC Driver version - "10.2.0.2.0"]
Without "and r.otherData is null" it works, so I don't see what the problem is with that?
otherData is a list and it is defined in the Entry class that way:
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "ENTRY_ID", referencedColumnName = "ID")
private List<OtherData> otherData;
OtherData (name changed) class:
#Entity
#Table(name = "OTHERDATA")
#SequenceGenerator(name = "SEQ", allocationSize = 1, sequenceName = "SEQ_OTHERDATA_ID")
public class OtherData {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ")
private Long id;
...
#Column(name = "ENTRY_ID")
private Long entryId;
Can I not use "is null" on lists?
EDIT
I tried it with "size(r.otherData) = 0" and with "r.otherData IS EMPTY", but both led to the same exception.
What DOES work is this:
Query queryDeleteEntries = entityManager.createQuery("DELETE from Entry r where id in "
+ "(select id from Entry r2 where r2.person.emailAddress like :name"
+ " and not exists (select 1 from OtherData a where a.entryId = r2.id))");
int deletedEntriesCount = queryDeleteEntries.setParameter("name", "tester."+emailAddressSuffix).executeUpdate();
But I think it shouldn't be that complicated! Isn't there an easier way to do that?
Thanks in advance for any hints!
I would recommend taking the generated query and see how hibernate is translating to native Oracle query, by logging the hibernate packages or simply make add this to hibernate.cfg.xml :
<property name="show_sql">true</property>
also try to add .otherData.idOtherData just to see the behaviour.
Related
I believe the code below is working correctly but my logic is flawed and so I'm looking for help getting the results I actually want. In addition I think my attempt on the JPA method is overly convoluted and there's likely a cleaner solution. I know I can likely use #Query but I want to try and get the method name solution working as well so I can learn where I went wrong.
For simplicity I've stripped back the DB tables in this example, however I have Table1 which is the top level table and Table2 which sits below it and has a foreign key back to Table1.
Table 1 -> Table 2 is a one to many relationship, I am looking to write a JPA method that will pull back a record from Table 1 when given the Table1 ID and only the current effective record from Table2 (this will be where EffectiveDateTime column is most recent but NOT future dated). Using the example below I want it to only pull back ID 8.
Unfortunately, I believe with the code I have below it recognises one of those records for Table2 is within the date range required and is therefore pulling all the records back that have a relation with the ID from Table 1. I could be wrong on this though and the logic could be flawed in a different way.
Any help would be appreciated.
Table1 Entity Class
#Entity
#Table(name = "TABLE1")
public class Table1 {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", nullable = false, unique = true)
private Integer id;
#OneToMany(mappedBy = "table1", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Fetch(value = FetchMode.SUBSELECT)
#JsonManagedReference
private List<Table2> table2;
//Plus getters & setters
Table2 Entity Class
#Entity
#Table(name = "TABLE2")
public class Table2{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", nullable = false, unique = true)
private Integer id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "Table1Id")
#JsonBackReference
private Table1 table1;
#Column(name = "EffectiveDateTime", nullable = false)
private LocalDateTime effectiveDateTime;
//Plus getters & setters
Repo Class
#Repository
public interface Repository extends JpaRepository<Table1, Integer>, JpaSpecificationExecutor<Table1> {
public Optional<Table1> findTopByIdAndTable2EffectiveDateTimeLessThanEqualOrderByTable2EffectiveDateTimeDesc(int id, LocalDateTime now);
}
Current JSON return
{
"id": 5
"Table2": [
{
"id": 6,
"effectiveDateTime": "2021-01-01T00:00:01"
},
{
"id": 8,
"effectiveDateTime": "2022-01-01T00:00:01"
},
{
"id": 9,
"effectiveDateTime": "2023-01-01T00:00:01"
}
]
}
Once you declared a oneToMany relationship, your entity will load all joined entity when calling the getTable method (wich is called by jackson when serializing). I believe if you activate debug mode, you will see 2 request, the first will be the one you describe in your repopsitory, the second the one JPA use to load all related table2 entity.
Their is some possible ways to accomplish what you want:
the first (and most obvious ?) is to manually make the 2 request equivalent to your HQL request (the table 1 find by ID and then the table 2 find by....) and then aggregate the 2 results
the second one would be to use jackson to serialize your data how you want to (by filtering useless table 2 data)
the third one would be to use jackson to serialize a table2 (wich give you the corresponding table1 data) into the appropriate JSON. It's a bit more complicated but probably more efficient if your table1 reference a lot of table2 data.
The last one would be to try using projections it allow you to declare only the data you want to retrieve when requesting data.
hope this help !
In the end we completed this using a JPQL query. An example of the query is below in case it helps any others in the future.
#Query(value = "SELECT table1 FROM Table1 table1 "
+ " JOIN FETCH table1.table2 table2 "
+ " WHERE table1.id = :table1Id"
+ " AND table2.id = "
+ " ( SELECT table2.id FROM table2 "
+ " WHERE table2.table1.id = :table1Id "
+ " AND table2.effectiveDateTime = "
+ " ( SELECT MAX(effectiveDateTime) FROM table2"
+ " WHERE table2.table1.id = :table1Id "
+ " AND table2.effectiveDateTime <= :currentTimestamp "
+ " ) "
+ " ) ")
Optional<Table1> getEffectiveDateTimeTable2(#Param("table1Id") int table1Id, #Param("currentTimestamp") LocalDateTime currentTimestamp);
Below is the entity configuration:
#Entity
#SequenceGenerator(
name = "sessionInfoIdSeq",
sequenceName = "SESSIONINFO_ID_SEQ"
)
public class SessionInfo implements Transformable {
#Id
#GeneratedValue(
strategy = GenerationType.AUTO,
generator = "sessionInfoIdSeq"
)
private Long id;
This means when inserting data into the database, the id will be fetched from the SESSIONINFO_ID_SEQ:
select nextval ('SESSIONINFO_ID_SEQ')
But the problem is, the next sequence number get by Spring boot app + Hibernate is not being the same as when we run the native query in DataGrip or DBeaver, although I've seen the app used the same query that used in Datagrip.
Spring boot + hibernate at runtime: 12749
Running native query in DataGrip: 12797
I'm not sure why this is appearing. But the question is how can I sync the sequence number, to when the app takes a new one, we can see the same on Datagrip or DBeaver.
Let me know if the question is existing or not correct. Thank you in advance for all your support.
Use can use attribute level annotations as follows:
#Id
#GeneratedValue(generator = "sequence-generator")
#GenericGenerator(
name = "sequence-generator",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
#Parameter(name = "sequence_name", value = "SESSIONINFO_ID_SEQ"),
#Parameter(name = "initial_value", value = "4"),
#Parameter(name = "increment_size", value = "1")
}
)
private long id;
I need to fetch a nested field called tabPremissa into Premissa model, but I can't handle JavaAssistLazyInitializer.
I Already tried
unproxy the lazy field with ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation()
INNER JOIN FETCH on JpaRepository method
The code is as follows:
Premissa.java
#Entity
#Table(name = "premissa")
public class Premissa implements Draggable {
#Id
#SequenceGenerator(name = SEQ, sequenceName = SEQ, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ)
#Column(name = "id")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_subcategoria_premissa", nullable=false)
private SubCategoriaPremissa subCategoriaPremissa;
}
SubCategoriaPremissa.java
#Entity
#Table(name = "subcategoria_premissa")
public class SubCategoriaPremissa implements Draggable {
#Id
#SequenceGenerator(name = SEQ, sequenceName = SEQ, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ)
#Column(name = "id")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_tab_premissa", nullable=false)
private TabPremissa tabPremissa;
}
#Query
String QUERY_TAB_ORDER_BY_TAB_AND_ORDER = " SELECT P From Premissa P "
+ " INNER JOIN FETCH P.subCategoriaPremissa SCP "
+ " INNER JOIN FETCH SCP.tabPremissa TP "
+ " WHERE TP in :tabs "
+ " ORDER BY SCP.tabPremissa, P.ordem ";
Hibernate log
select
premissa0_.id as id1_70_0_,
subcategor1_.id as id1_85_1_,
tabpremiss2_.id as id1_86_2_,
premissa0_.campo_1 as campo_2_70_0_,
premissa0_.campo_2 as campo_3_70_0_,
premissa0_.campo_3 as campo_4_70_0_,
premissa0_.campo_4 as campo_5_70_0_,
premissa0_.id_centro_custo as id_cent15_70_0_,
premissa0_.considera_zero as consider6_70_0_,
premissa0_.descricao as descrica7_70_0_,
premissa0_.id_empresa as id_empr16_70_0_,
premissa0_.id_grupo_economico as id_grup17_70_0_,
premissa0_.id_clone as id_clone8_70_0_,
premissa0_.logica_totalizador as logica_t9_70_0_,
premissa0_.nome as nome10_70_0_,
premissa0_.ordem as ordem11_70_0_,
premissa0_.id_pai as id_pai18_70_0_,
premissa0_.style_table as style_t12_70_0_,
premissa0_.id_subcategoria_premissa as id_subc19_70_0_,
premissa0_.tipo_operacao_premissa as tipo_op13_70_0_,
premissa0_.unidade_medida as unidade14_70_0_,
subcategor1_.id_clone as id_clone2_85_1_,
subcategor1_.label_1 as label_3_85_1_,
subcategor1_.label_2 as label_4_85_1_,
subcategor1_.label_3 as label_5_85_1_,
subcategor1_.label_4 as label_6_85_1_,
subcategor1_.nome as nome7_85_1_,
subcategor1_.ordem as ordem8_85_1_,
subcategor1_.id_pai as id_pai9_85_1_,
subcategor1_.id_tab_premissa as id_tab_10_85_1_,
tabpremiss2_.id_categoria_premissa as id_categ8_86_2_,
tabpremiss2_.definicao_json as definica2_86_2_,
tabpremiss2_.enum_link_id as enum_lin3_86_2_,
tabpremiss2_.hexa_bg_color as hexa_bg_4_86_2_,
tabpremiss2_.nome as nome5_86_2_,
tabpremiss2_.ordem as ordem6_86_2_,
tabpremiss2_.id_pai as id_pai9_86_2_,
tabpremiss2_.status_edit as status_e7_86_2_
from
premissa premissa0_
inner join
subcategoria_premissa subcategor1_
on premissa0_.id_subcategoria_premissa=subcategor1_.id
inner join
tab_premissa tabpremiss2_
on subcategor1_.id_tab_premissa=tabpremiss2_.id
where
tabpremiss2_.id in (
? , ?
)
order by
subcategor1_.id_tab_premissa,
premissa0_.ordem
EDIT
I searched one example directly on database where Premissa p has a SubcategoriaPremissa s and ran the following commands:
s = subCategoriaPremissaRepository.findOne(1883L);
p = premissaRepository.findOne(9019L);
In this case, every data is loaded properly and s is located in p.
However, if the order of execution is changed, s is considered JavaAssistLazyInitializer while debugging
The problem you are most likely having is that Hibernate is unable to merge fetched state into existing objects already present in your first level cache a.k.a. your EntityManager/Session. So if you somehow get an entity proxy into your EntityManager/Session, it doesn't matter if subsequent queries fetch that entity, the result will always be the proxy. Hibernate must adhere to the JPA specification that roughly says, managed entities with the same primary key must only exist once object-identity-wise in an EntityManager/Session.
This is IMO one of the biggest reasons to avoid the OSIV(Open-Session-In-View) anti-pattern, but is rarely talked about. Hibernate might at some point fix this, but for now, this is how it behaves.
So one thing you can do right now is to invoke EntityManager.clear() to clear the whole first level cache or EntityManager.detach() with the problematic entity as argument, to remove just that from the first level cache.
I have two Entities
#Entity
#Table(name = "steps")
public class Step {
#Id
#Column(nullable = false, insertable = true, updatable = true)
private long id;
//more fields
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "result_id", referencedColumnName = "id")
private Result result;
and
#Entity
#Table(name = "results")
public class Result {
#Id
#Column(nullable = false, insertable = true, updatable = true)
private long id;
//more fields
#OneToMany(mappedBy = "result", fetch = FetchType.EAGER)
private List<Step> steps;
Now I need to get all results and their associated steps.
I will want all the steps so an eager fetch seems best, and I know all results will have steps so an inner join will be fine.
I will want to get only some more fields from results, but not all so I can maximize the use of an index, so projection will be needed.
To accomplish this I have tried two methods:
Query query = getSession().createQuery(
"select distinct result from Result as result " +
"inner join result.steps " +
"where result.monitorId = :id " +
"and result.completed between :from and :to ");
query.setLong("id", monitorId);
query.setTimestamp("from", from);
query.setTimestamp("to", to);
for (Result result : query.list()) {
result.getSteps()
//work work
}
Hibernate does a join like I wanted but when I start to iterate over the results, Hibernate logs one more select query per step I'm using.
(I also haven't found a good way for projection if I'm going this route?)
The second approach I have tried seems great so far:
Criteria criteria = getSession().createCriteria(Result.class);
criteria.setProjection(Projections.projectionList()
.add(Projections.property("completed"), "completed")
.add(Projections.property("steps"), "steps"));
criteria.add(Restrictions.eq("someId", someId));
criteria.add(Restrictions.between("completed", from, to));
criteria.setFetchMode("steps", FetchMode.JOIN);
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
criteria.setResultTransformer(Transformers.aliasToBean(Result.class));
The projections work great as long as I don't include steps. I'm guessing that has to do with it not belonging to the actual database table? When I do try with steps I get
java.lang.ArrayIndexOutOfBoundsException: 2
on criteria.list();
So my main question is, how do I best do eager fetching of some but not all columns in results + a collection of steps belonging to each result?
Help please? :)
I have a problem with JPA querying a MySQL table that has a column of type geometry. It contains polygons having sets of latitude and longitude as the coordinates. While executing the nativequery to select from the table, I am getting the following error
Exception Description: The primary key read from the row [ArrayRecord(
=> POLYGON((102.642944444444 2.9757087270706,102.642944444444 2.79805447470818,....
=> 16.0
=> 325990)] during the execution of the query was detected to be null. Primary keys must not contain null.
However the table has no row with primary key as null. This specific row has a very large polygon with 66 coordinates. Not sure if the problem is because of this.
Following are the table column names and types
geomarea - geometry
riskvalue - double
id - int (Autoincrement, Primary Key)
Following is the code in my EJB to read the table.
Query query = em.createNativeQuery("select astext(geomarea) geomarea,riskvalue,id from earthquakeRisk where Contains(geomarea,GeomFromText('POINT(" + node.getLongitude() + " "+node.getLatitude()+")'))",Earthquakerisk.class);
geomList.addAll(query.getResultList());
And here is how the fields are declared in the entity class
public class Earthquakerisk implements Serializable {
private static final long serialVersionUID = 1L;
#Basic(optional = false)
#NotNull
#Lob
#Column(name = "geomArea")
private byte[] geomArea;
#Column(name = "riskvalue")
private Double riskvalue;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
Any idea how to solve this?
I found a solution to the problem. Adding it here in case someone finds it useful.
Solved it by removing the Earthquakerisk.class from the query, and changing my List to List. So the working code is now as follows.
List<Object[]> geomList = new ArrayList<>();
Query query = em.createNativeQuery("select astext(geomarea) geomarea,riskvalue,id from earthquakeRisk where Contains(geomarea,GeomFromText('POINT(" + node.getLongitude() + " "+node.getLatitude()+")'))");
geomList.addAll(query.getResultList());