I am trying the following which is resulting in an additional update execution and failing my tests.
I have an entity like this.
#Entity
#SqlResultSetMapping(name = "tempfilenameRSMapping",
entities = { #EntityResult(entityClass = MyEntity.class) },
columns = { #ColumnResult(name = "TEMPFILENAME") })
//The reason for this mapping is to fetch an additional field data through join.
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#Column(name="ID")
private String id;
#Column(name="NAME")
private String name;
#Column(name="DESC")
private String description;
#Column(name="STATUS")
private String status;
//follwed by getter setters
}
I am trying to do a retrieve with a native query. And for the retrieved entity, I execute a native update (the reason for native update is that I want to update just one single field). Note that I am not updating the retrieved entity directly.
What I observe is that my update is not getting executed properly. When I turn the TRACE on, I notice that on flush openJPA is executing an additional update query and therefore overriding my original update.
e.g.
SELECT M.ID, M.NAME, M.DESC, O.TEMPFILENAME FROM MY_TABLE M, OTHER_TABLE O WHERE M.ID = ?
UPDATE MY_TABLE SET STATUS = ? WHERE ID = ?
UPDATE MY_TABLE SET ID=?, NAME=?, DESC=?, STATUS=? WHERE ID = ?
What can I do to skip the auto-updation?
Edit:
Here are the routines we use for executing the queries.
The following routine returns a named native query sql.
public String getNamedNativeQuerySql(EntityManagerFactory emf, String qryName) {
MetamodelImpl metamodel = (MetamodelImpl) emf.getMetamodel();
QueryMetaData queryMetaData =
metamodel.getConfiguration().getMetaDataRepositoryInstance().getQueryMetaData(null, qryName, null, true);
String queryString = queryMetaData.getQueryString();
return queryString;
}
The code for retrieval:
Query query = entityManager.createNamedQuery("retrieveQry");
query.setParameter(1, id);
Object[] result = (Object[]) query.getSingleResult();
MyEntity entity = (MyEntity) result[0];
String tempFileName = (String) result[1];
The code for update that follows retrieval:
Query qry = entityManager.createNamedQuery("updateQry");
qry.setParameter(1, status);
qry.setParameter(2, entity.getId() );
qry.executeUpdate()
Edit:
I see the problem even without the update statement. OpenJPA is
executing an additional update query even if I do a simple find.
The problem was with runtime enhancement. OpenJPA was unable to do a proper detection of dirty state with runtime-enhanced entities.
It got resolved by doing a build time enhancement.
Related
I have the following JPA entities
#Entity
#Table(name="application_user")
public class ApplicationUser {
#Id
#Column(name="user_id")
private String userid;
#Column(name="last_write_time")
private Instant lastWrite;
//other fields omitted
}
#Entity
#Table(name="demographic")
public class Demographic {
#Id
#Column(name="user_id")
private String userid;
//primary key is a foreign key link
#OneToOne
#PrimaryKeyJoinColumn(name="user_id", referencedColumnName="user_id")
private ApplicationUser user;
//other fields omitted
}
My goal is to retrieve all of the Demographics that contains users where the last write time is the max value in the column. I pretty much want to write the following SQL using the JPA CriteriaBUilder
select * from demographic where
userid in (
select userid from application_user where
last_write in (
select max(last_write) from application_user
)
)
I tried writing the following CriteriaBuilder Code to accomplish this goal and it compiles successfully. Note I am using the generated Metamodel classes.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Demographic> c = cb.createQuery(Demographic.class);
Root<Demographic> root = c.from(Demographic.class);
root.fetch(Demographic_.user, JoinType.INNER);
Subquery<Instant> sqLatestUsers = c.subquery(Instant.class);
Root<ApplicationUser> subRootLatestUsers = sqLatestUsers.from(ApplicationUser.class);
sqLatestUsers.select(cb.greatest(subRootLatestUsers.<Instant>get(ApplicationUser_.LAST_WRITE)));
Predicate predicateLatestUsers = subRootLatestUsers.get(ApplicationUser_.LAST_WRITE).in(sqLatestUsers);
Subquery<ApplicationUser> sq = c.subquery(ApplicationUser.class);
Root<Demographic> subRoot = sq.from(Demographic.class);
sq.select(subRoot.<ApplicationUser>get(Demographic_.USER)).where(predicateLatestUsers);
Predicate containsUsers = subRoot.get(Demographic_.USER).in(sq);
c.select(root).where(containsUsers);
The code compiles and successfully deploys in Wildfly 14, but when I execute the code, the get the following error (with white space to improve readability):
Invalid path: 'generatedAlias2.user' : Invalid path: 'generatedAlias2.user'
...
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias2.user' [
select generatedAlias0 from com.company.model.Demographic as generatedAlias0
inner join fetch generatedAlias0.user as generatedAlias1
where generatedAlias2.user in (
select generatedAlias2.user from com.company.model.Demographic as generatedAlias2 where generatedAlias3.lastWrite in (
select max(generatedAlias3.lastWrite) from com.company.model.StarfishUser as generatedAlias3
)
)
]
Is chaining subqueries (nested subqueries) allowed by the JPA spec? Did I find something that is syntactically correctly but not actually allowed?
I figure out how to get the subquery to work. First is my updated Utility method
public static <R, T> Subquery<T> getLatestSubelement(CriteriaBuilder cb, CriteriaQuery<R> c, Class<T> clazz, SingularAttribute<T, Instant> attribute) {
//Get latest timestamp
Subquery<Instant> sq = c.subquery(Instant.class);
Root<T> subRoot = sq.from(clazz);
sq.select(cb.greatest(subRoot.<Instant>get(attribute)));
//Get object with the latest timestamp
Subquery<T> sq2 = c.subquery(clazz);
Root<T> subRoot2 = sq2.from(clazz);
sq2.where(subRoot2.get(attribute).in(sq));
return sq2;
}
Here is the code that uses the utility method
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Demographic> c = cb.createQuery(Demographic.class);
Root<Demographic> root = c.from(Demographic.class);
joinType = JoinType.INNER;
//use fetch instead of join to prevent duplicates in Lists
root.fetch(Demographic_.user, joinType);
Subquery<ApplicationUser> sq = JpaUtil.getLatestSubelement(cb, c, ApplicationUser.class, ApplicationUser_.lastWrite);
c.where(root.get(Demographic_.user).in(sq));
TypedQuery<Demographic> q = em.createQuery(c);
Stream<Demographic> stream = q.getResultStream();
I'm trying to use sequences to generate incremented IDs for my tables in DB2. It works when I send SQL statements directly to the database, but when using ebean the statement fails. Here's the field in Java:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TABLENAME_IDNAME_TRIG")
#SequenceGenerator(name = "TABLENAME_IDNAME_TRIG", sequenceName = "TABLENAME_IDNAME_SEQ")
#Column(name = "IDNAME")
private Long id;
Here's the column in SQL (From TOAD):
Name Data type Not Null Default Generated Bit Data Scope Identity
IDNAME INTEGER Yes No No
And here's the sequence definition in SQL:
CREATE OR REPLACE SEQUENCE SCHEMA.TABLENAME_IDNAME_SEQ
AS INTEGER CACHE 50 ORDER;
And the trigger:
CREATE OR REPLACE TRIGGER SCHEMA.TABLENAME_IDNAME_TRIG
NO CASCADE BEFORE INSERT
ON TABLENAME
REFERENCING
NEW AS OBJ
FOR EACH ROW
BEGIN
SET obj.IDNAME=NEXT VALUE FOR SCHEMA.TABLENAME_IDNAME_SEQ;
END;
What is the issue with my annotations here? As a(n important) side note - when I set GenerationType to AUTO, TABLE, or IDENTITY, it works, even though it shouldn't, because I'm also using this object to represent a parallel oracle table which also uses sequences for ID generation.
Edited to include error message:
javax.persistence.PersistenceException: Error getting sequence nextval
...
Caused by: com.ibm.db2.jcc.am.SqlSyntaxErrorException: DB2 SQL Error: SQLCODE=-348, SQLSTATE=428F9, SQLERRMC=NEXTVAL FOR SCHEMA.TABLENAME_IDNAME_SEQ, DRIVER=4.19.49
EDIT 2: The specific Sql statement that is failing is:
values nextval for QA_CONNECTION_ICONNECTIONI_SEQ union values nextval for QA_CONNECTION_ICONNECTIONI_SEQ union values nextval for QA_CONNECTION_ICONNECTIONI_SEQ
Which is SQL generated by Ebean. This is a smaller version of the real statement, which is repeated 20 times, so I'm guessing something screws up when generating the caching query.
EDIT 3: I believe this might be a bug in Ebean's use of DB2 sequences. This function generates SQl that returns an error for me when used with db2
public DB2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
super(be, ds, seqName, batchSize);
this.baseSql = "values nextval for " + seqName;
this.unionBaseSql = " union " + baseSql;
}
EDIT 4: Based on this SO link I think it is a bug.
Can't insert multiple values into DB2 by using UNION ALL and generate IDs from sequence
The correct class probably looks like this? Though I haven't ever tried building the library, so I couldn't test it. Time to learn how to open a defect I guess.
public class DB2SequenceIdGenerator extends SequenceIdGenerator {
private final String baseSql;
private final String unionBaseSql;
private final String startSql;
public DB2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
super(be, ds, seqName, batchSize);
this.startSql = "values "
this.baseSql = "(nextval for " + seqName);
this.unionBaseSql = ", " + baseSql;
}
public String getSql(int batchSize) {
StringBuilder sb = new StringBuilder();
sb.append(startSql);
sb.append(baseSql);
for (int i = 1; i < batchSize; i++) {
sb.append(unionBaseSql);
}
return sb.toString();
}
}
Temporary workaround for those interested: in ebean.properties, set
ebean.databaseSequenceBatchSize=1
Our project is currently migrating to EF(and away from Stored Procs), and one of the enhancements(we're adding to the architecture) is using Optimistic Concurrency when users save data to the database(we currently don't have this feature). I'm having problems getting EF to fail when it should. In other words when two users open the same record, each make changes and attempts to save those changes the first to save update the record, and the second would get an error message. I created a simple example to illustrate my problem.
In the database I have the following table(and insert test data):
Create Table Work
(
Id int identity(1,1) Primary Key
,UserIdAssignTo int null
,RowVer RowVersion not null
)
Insert Into Work(UserIdAssignTo)Values(1)
I created an EF file (.edmx) and drag/drop the table, above, onto the canvas. I updated the properties on the property/column RowVer as follows:
RowVer Property/Column
Concurrency Mode: Fixed
Getter/Setter are both Public
Nullable: False
Store Generated: Computed
Type: Binary
I have an object that will retrieve and update the table like below:
public class Work
{
public int Id { get; set; }
public int? UserIdAssignTo { get; set; }
public byte[] Version { get; set; }
private string _conn = String.Empty;
public WorkData()
{
_conn = GetConnectionsString();
}
public void GetById(int WorkID)
{
using (SQL context = new SQL(_conn))
{
Work fromDb = context.Works.FirstOrDefault(db => db.Id == WorkID);
if (fromDb != null)
{
Id = fromDb.Id;
UserIdAssignTo = fromDb.UserIdAssignTo;
Version = fromDb.RowVer;
}
}
}
public void Update()
{
using (SQL context = new SQL(_conn))
{
Work fromDb = context.Works.FirstOrDefault(db => db.Id == Id);
if (fromDb != null)
{
fromDb.UserIdAssignTo = UserIdAssignTo;
fromDb.RowVer = Version;
context.SaveChanges();
UserIdAssignTo = fromDb.UserIdAssignTo;
Version = fromDb.RowVer;
}
}
}
}
I developed a test case to expose the error I'm getting:
[Test]
public void ConcurencyDataTest()
{
WorkData first = new WorkData();
first.GetById(1);
WorkData second = new WorkData();
second.GetById(1);
first.UserIdAssignTo = null;
first.Update();
second.UserIdAssignTo = 1;
second.Update(); // I should get an exception b/c the object is outdated
}
After both "first" and "second" object call the GetById(1) method, their RowVer property is the same for both objects(as expected).
I ran SQL profiler when I executed this test
The below is when the "first" object called Update method
exec sp_executesql N'update [dbo].[Work]
set [UserIdAssignTo] = null
where (([Id] = #0) and ([RowVer] = #1))
select [RowVer]
from [dbo].[Work]
where ##ROWCOUNT > 0 and [Id] = #0',N'#0 int,#1 binary(8)',#0=1,#1=0x00000000024E6E2
Note the #1 parameter, both the "first" and "second" object should have that in memory and use it when update
When second.Update was called, the SQL profiler recorded this:
exec sp_executesql N'update [dbo].[Work]
set [UserIdAssignTo] = #0
where (([Id] = #1) and ([RowVer] = #2))
select [RowVer]
from [dbo].[Work]
where ##ROWCOUNT > 0 and [Id] = #1',N'#0 int,#1 int,#2 binary(8)',#0=1,#1=1,#2=0x00000000024E6E2F
Note the #1 parameter has changed to the new value(after "first" updated), when it should be the old value that was held by the object "second"(the old value is 0x00000000024E6E2). I don't understand how it got changed and I'm a little confused on how to properly implement first write concurrency through EF.
The results I'm actually getting is the "second" object is successfully updating the table, when it should be failing.
Edit: This to simulate using an N-tier architecture. I'm trying to update with detached objects.
I think it is because in your update method you retrieve the object again from the context which would get the current value of RowVer. Since it is computed I don't think setting it back to the previous version would work. So when it updates it does have the current value of RowVer that is in the table.
I think you instead would need to attach or add the object to the context.
http://msdn.microsoft.com/en-us/library/bb896271.aspx
http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx
I'm currently planning on switching my "manual query-writing" code to a nice SQL framework, so I can leave the queries or sql things to the framework, instead of writing the queries myself.
Now I'm wondering how I can get a single record from my table in Entity Framework 4?
I've primarily used SQL like SELECT * FROM {0} WHERE Id = {1}. That doesn't work in EF4, as far as I'm concerned.
Is there a way I can select a single ID-Based record from my Context?
Something like:
public Address GetAddress(int addressId)
{
var result = from Context.Addresses where Address.Id = addressId;
Address adr = result as Address;
return Address;
}
Thank you!
var address = Context.Addresses.First(a => a.Id == addressId);
You can use Single or First methods.
The difference between those methods is that Single expects a single row and throws an exception if it doesn't have a single row.
The usage is the same for both of them
(Based on VS 2015) If you create an .edmx (Add --> ADO.NET Entity Data Model).
Go through the steps to created the ".edmx" and use the following to run the stored procedure. emailAddress is the parameter you are passing to the stored procedure g_getLoginStatus. This will pull the first row into LoginStatus and status is a column in the database:
bool verasity = false;
DBNameEntities db = new DBNameEntities(); // Use name of your DBEntities
var LoginStatus = db.g_getLoginStatus(emailAddress).FirstOrDefault();
if ((LoginStatus != null) && (LoginStatus.status == 1))
{
verasity = true;
}
I have a very basic relationship between two objects:
#Entity
public class A {
#ManyToOne(optional = false)
#JoinColumn(name="B_ID", insertable=false, updatable=true)
private StatusOfA sa;
getter+setter
}
#Entity
public class StatusOfA {
#Id
private long id;
#Column
private String status;
getter+setter
}
There's only a limited set of StatusOfA in DB.
I perform an update on A in a transaction:
#TransactionalAttribute
public void updateStatusOfA(long id) {
A a = aDao.getAById(123);
if(a != null) {
a.getStatusOfA().getId(); //just to ensure that the object is loaded from DB
StatusOfA anotherStatusOfA = statusOfADao.getStatusOfAById(456);
a.setStatusOfA(aontherStatusOfA);
aDao.saveOrPersistA(a);
}
}
The saveOrPersistA method is here merging 'a'.
I expect Eclipselink to perform only an update on 'a' to update the StatusOfA but it's executing a new insert on StatusOfA table. Oracle is then complaining due to a unique contraint violation (the StatusOfA that Eclipselink tries to persist already exists...).
There is no Cascading here so the problem is not there and Hibernate (in JPA2) is behaving as excepted.
In the same project, I already made some more complex relationships and I'm really surprised to see that the relation here in not working.
Thanks in advance for your help.
What does, statusOfADao.getStatusOfAById() do?
Does it use the same persistence context (same transaction and EntityManager)?
You need to use the same EntityManager, as you should not mix objects from different persistence contexts.
What does saveOrPersistA do exactly? The merge() call should resolve everything correctly, but if you have really messed up objects, it may be difficult to merge everything as you expect.
Are you merging just A, or its status as well? Try also setting the status to the merged result of the status.
Assumptions: #Id#GeneratedValue(strategy = GenerationType.IDENTITY)
Let's consider the following implementations of statusOfADao.getStatusOfAById(456) :
1. returns "proxy" object with just id set:
return new StatusOfA(456);
2. returns entity in new transaction:
EntityManager em = emf.createEntityManager();em.getTransaction().begin();
StatusOfA o = em.find(StatusOfA.class,456);//em.getReference(StatusOfA.class,456);
em.getTransaction().commit();
return o;
3. returns detached entity:
StatusOfA o = em.find(StatusOfA.class,456);//em.getReference(StatusOfA.class,456);
em.detached(o);
return o;
4. returns deserialized-serialized entity:
return ObjectCloner.deepCopy(em.find(StatusOfA.class,456));
5. returns attached entity:
return em.find(StatusOfA.class,456);
Conclusions:
Eclipselink handles only implementation N5 as "expected".
Hibernate handles all five implementations as "expected".
No analisys of what behaviour is jpa spec compliant