Spring Data: Method with annotation #Query doesn't work as expected - java

I try to cover my Repository code with junit tests but unexpectedly I am facing the following problem:
#Test
#Transactional
public void shoudDeactivateAll(){
/*get all Entities from DB*/
List<SomeEntity> someEntities = someEntityRepository.findAll();
/*for each Entity set 1 for field active*/
someEntities.forEach(entity ->
{entity.setActive(1);
/*save changes*/
SomeEntityRepository.save(entity);});
/*call service, which walks through the whole rows and updates "Active" field to 0.*/
unActiveService.makeAllUnactive();
/*get all Entities again
List<SomeEntity> someEntities = SomeEntityRepository.findAll();
/*check that all Entities now have active =0*/
someEntities.forEach(entity -> {AssertEquals(0, entity.getActive());});
}
where:
makeAllUnactive() method is just a #Query:
#Modifying
#Query(value = "update SomeEntity e set v.active=0 where v.active =1")
public void makeAllUnactive();
And: someEntityRepository extends JpaRepository
This test method return AssertionError: Expected 0 but was 1.
it means that makeAllUnactive didn't change the status for Entitites OR did chanches, but they are invisible.
Could you please help me understand where is "gap" in my code?

in the query you have:
#Query(value = "update SomeEntity e set v.active=0 where v.active =1")
you should rather have changed it into:
#Query(value = "update SomeEntity e set e.active=0 where e.active =1")
if that does not work, try flushing after running SomeEntityRepository.save(entity);
EDIT:
You should enable clearAutomatically flag in the #Modifying, so that EntityManager will get updated. However keep it mind that it may also cause loosing all the non-flushed changes. For some more reading take a look:
http://docs.spring.io/spring-data/jpa/docs/1.5.0.M1/reference/htmlsingle/#jpa.modifying-queries

Related

How to LOCK TABLE ... do stuff ... UNLOCK TABLE with Spring Boot?

The idea is basically to extend some Repositories with custom functionality. So I got this setup, which DOES work!
#MappedSuperclass
abstract class MyBaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int = 0
var eid: Int = 0
}
interface MyRepository<T : MyBaseEntity> {
#Transactional
fun saveInsert(entity: T): Optional<T>
}
open class MyRepositoryImpl<T : MyBaseEntity> : MyRepository<T> {
#Autowired
private lateinit var entityManager: EntityManager
#Transactional
override fun saveInsert(entity: T): Optional<T> {
// lock table
entityManager.createNativeQuery("LOCK TABLE myTable WRITE").executeUpdate()
// get current max EID
val result = entityManager.createNativeQuery("SELECT MAX(eid) FROM myTable LIMIT 1").singleResult as? Int ?: 0
// set entities EID with incremented result
entity.eid = result + 1
// test if table is locked. sending manually 2-3 POST requests to REST
Thread.sleep(5000)
// save
entityManager.persist(entity)
// unlock
entityManager.createNativeQuery("UNLOCK TABLES").executeUpdate()
return Optional.of(entity)
}
}
How would I do this more spring-Like?
At first, I thought the #Transactional would do the LOCK and UNLOCK stuff. I tried a couple of additional parameters and #Lock. I did go through docs and some tutorials but the abstract technical English is often not easy to understand. At the end, I did not get a working solution so I manually added the table-locking, which works fine. Still would prefer a more spring-like way to do it.
1) There might be a problem with your current design as well. The persist does not instantly INSERT a row in the database. That happens on transaction commit when the method returns.
So you unlock the table before the actual insert:
// save
entityManager.persist(entity) // -> There is no INSERT at this point.
// unlock
entityManager.createNativeQuery("UNLOCK TABLES").executeUpdate()
2) Going back to how to do it only with JPA without natives (it still requires a bit of a workaround as it is not supported by default):
// lock table by loading one existing entity and setting the LockModeType
Entity lockedEntity = entityManager.find(Entity.class, 1, LockModeType.PESSIMISTIC_WRITE);
// get current max EID, TRY NOT TO USE NATIVE QUERY HERE
// set entities EID with incremented result
// save
entityManager.persist(entity)
entityManager.flush() // -> Force an actual INSERT
// unlock by passing the previous entity
entityManager.lock(lockedEntity, LockModeType.NONE)

stored procedure not returning value on SQL Server

i have a stored procedure which i am trying to execute from my DAO implementation class. but i am failing to get any result. on my list i see return values as null.
my DAO code which executes stored procedure
Edited Dao:
#Repository
public class OhlcDaoImpl implements OhlcDao {
#Autowired
SessionFactory sessionFactory;
Session session;
public List getOhlc(Result result) {
session = sessionFactory.openSession();
SQLQuery query = session.createSQLQuery("EXEC getOhlc :stockSymbol, :statementType, :financialDataTypeId, :fiscalYearId");
query.setString("stockSymbol",result.getStockSymbol());
query.setString("statementType",result.getStatementType());
query.setInteger("financialDataTypeId",result.getFinancialDataTypeId());
query.setInteger("fiscalYearId",result.getFiscalYearId());
List l = query.list();
return l;
}
}
I tried to change setparameters to setInteger for int field but still no result.
DefaultController.java
controller class calls service and service calls dao. service has nothing but one interface class with getData method and one implementation class.
#RequestMapping(value = "/getData",method = RequestMethod.POST)
public List getData(#RequestBody Result result){
List l =ohlcService.getData(result);
return l;
}
my stored procedure
USE [Wealth_1_0_0_Srv]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER Procedure [dbo].[getOhlc]
(
#stockSymbol varchar(50),
#statementType varchar(50),
#financialDataTypeId int,
#fiscalYearId int
)
As
Begin
Set Nocount on;
Set Arithabort on;
Select fdd.FinancialHeadId,isnull(ftd.FinancialHeadText,fh.FinancialHeadName)FinancialHeadName,fdd.Value,isnull(ftd.Bold,0)Bold,isnull(ftd.RowSeq,0)RowSeq
from FinancialDataMast fdm
inner join FinancialDataDetl fdd on fdd.MastId=fdm.Id
inner join FinancialHeading fh on fh.Id=fdd.FinancialHeadId
inner join Stock s on s.Id=fdm.StockId
INNER JOIN StatementType st ON st.Id = fdm.StatementTypeId
left outer join FinancialTemplateMast ftm on ftm.StatementTypeId=fdm.StatementTypeId and ftm.SectorId=s.SectorId and ftm.FinancialReportType=(Case fdm.FinancialDataTypeId when 0 then 2 else 1 end)
left outer join FinancialTemplateDetl ftd on ftd.MastId=ftm.Id and ftd.FinancialHeadId=fdd.FinancialHeadId
where s.StockSymbol=#stockSymbol and st.StatementTypeName=#statementType and fdm.FinancialDataTypeId=#financialDataTypeId and fdm.FiscalYearId=#fiscalYearId
order by isnull(ftd.RowSeq,0) asc
End
my parameters are fine. on debug mode i can see them but i am not getting any result set.
It looks like you are calling procedure, which doesn't return value-just perform query inside, you should create function.
Try this:
System.out.println(String.format("stockSymbol %s, statementType %s, financialDataTypeId %d, fiscalYearId %d", result.getStockSymbol(), result.getStatementType(), result.getFinancialDataTypeId(), result.getFiscalYearId()));
SQLQuery query = session.createSQLQuery("EXEC getOhlc :stockSymbol, :statementType, :financialDataTypeId, :fiscalYearId");
query.setString("stockSymbol",result.getStockSymbol());
query.setString("statementType",result.getStatementType());
query.setInteger("financialDataTypeId",result.getFinancialDataTypeId());
query.setInteger("fiscalYearId",result.getFiscalYearId());
List l = query.list();
Please add these lines in the hibernate configuration:
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<property name="use_sql_comments">true</property>
or if you use log4j add this:
log4j.logger.org.hibernate=INFO, hb
log4j.logger.org.hibernate.SQL=DEBUG
log4j.logger.org.hibernate.type=TRACE
log4j.appender.hb=org.apache.log4j.ConsoleAppender
log4j.appender.hb.layout=org.apache.log4j.PatternLayout
This way you can check what is executed.
Thank you all to all those who replied. the issue was not on proc. it was mapping issue so i mapped all returned columns like this
query.addScalar("FinancialHeadId", IntegerType.INSTANCE);
query.addScalar("FinancialHeadName", StringType.INSTANCE);
query.addScalar("Value",IntegerType.INSTANCE);
query.addScalar("Bold",IntegerType.INSTANCE);
query.addScalar("RowSeq",IntegerType.INSTANCE);
and the problem was solved.

org.springframework.orm.hibernate4.HibernateQueryException: Not supported for DML operations [UPDATE]

I am trying to update a record in the database and I getting this error
org.springframework.orm.hibernate4.HibernateQueryException: Not supported for DML operations [UPDATE com.xxx.models.User u set u.notifiable = true WHERE u.emailAccess = :emailAccess AND u.isAdmin = false]; nested exception is org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations [UPDATE com.xxx.models.User u set u.notifiable = true WHERE u.emailAccess = :emailAccess AND u.isAdmin = false]
This is my hql attempt
#Modifying
public User updateUser(String emailAccess) {
String hql = "UPDATE User u set u.notifiable = true WHERE u.emailAccess = :emailAccess AND u.isAdmin = false";
return (User) _sessionFactory.getCurrentSession().createQuery(hql).setParameter("emailAccess", emailAccess).list();
}
After researching, I added the #Modifying annotation to the top of the method but the error still persists. Please what could be wrong?
#Modifying is a Spring Data annotation and you do not appear to be using Spring Data so that is of no use to you.
You need to call executeUpdate() and make the return method void as executeUpdate
Returns: The number of entities updated or deleted..
https://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/Query.html#executeUpdate()
or return the result of a second query.
public void updateUser(String emailAccess) {
String hql = "UPDATE User u set u.notifiable = true WHERE u.emailAccess = :emailAccess AND u.isAdmin = false";
_sessionFactory.getCurrentSession().createQuery(hql).setParameter("emailAccess", emailAccess).executeUpdate();
}
You should invoke executeUpdate() on your query when you're going to update or delete your entities.
list() method which you're using is to select entities by given query, not to update them.
Your method signature and return types are incorrect. You can never be sure that update operation will actually update only a particular user. executeUpdate() method returns number of rows affected by your query, not the updated entity.
Moreover even list() from your original answer won't return a particular user. It (in case of select query) will return a list of users according to your conditions.

Spring data - insert data depending on previous insert

I need to save data into 2 tables (an entity and an association table).
I simply save my entity with the save() method from my entity repository.
Then, for performances, I need to insert rows into an association table in native sql. The rows have a reference on the entity I saved before.
The issue comes here : I get an integrity constraint exception concerning a Foreign Key. The entity saved first isn't known in this second query.
Here is my code :
The repo :
public interface DistributionRepository extends JpaRepository<Distribution, Long>, QueryDslPredicateExecutor<Distribution> {
#Modifying
#Query(value = "INSERT INTO DISTRIBUTION_PERIMETER(DISTRIBUTION_ID, SERVICE_ID) SELECT :distId, p.id FROM PERIMETER p "
+ "WHERE p.id in (:serviceIds) AND p.discriminator = 'SRV' ", nativeQuery = true)
void insertDistributionPerimeter(#Param(value = "distId") Long distributionId, #Param(value = "serviceIds") Set<Long> servicesIds);
}
The service :
#Service
public class DistributionServiceImpl implements IDistributionService {
#Inject
private DistributionRepository distributionRepository;
#Override
#Transactional
public DistributionResource distribute(final DistributionResource distribution) {
// 1. Entity creation and saving
Distribution created = new Distribution();
final Date distributionDate = new Date();
created.setStatus(EnumDistributionStatus.distributing);
created.setDistributionDate(distributionDate);
created.setDistributor(agentRepository.findOne(distribution.getDistributor().getMatricule()));
created.setDocument(documentRepository.findOne(distribution.getDocument().getTechId()));
created.setEntity(entityRepository.findOne(distribution.getEntity().getTechId()));
created = distributionRepository.save(created);
// 2. Association table
final Set<Long> serviceIds = new HashSet<Long>();
for (final ServiceResource sr : distribution.getServices()) {
serviceIds.add(sr.getTechId());
}
// EXCEPTION HERE
distributionRepository.insertDistributionPerimeter(created.getId(), serviceIds);
}
}
The 2 queries seem to be in different transactions whereas I set the #Transactionnal annotation. I also tried to execute my second query with an entityManager.createNativeQuery() and got the same result...
Invoke entityManager.flush() before you execute your native queries or use saveAndFlush instead.
I your specific case I would recommend to use
created = distributionRepository.saveAndFlush(created);
Important: your "native" queries must use the same transaction! (or you need a now transaction isolation level)
you also wrote:
I don't really understand why the flush action is not done by default
Flushing is handled by Hibernate (it can been configured, default is "auto"). This mean that hibernate will flush the data at any point in time. But always before you commit the transaction or execute an other SQL statement VIA HIBERNATE. - So normally this is no problem, but in your case, you bypass hibernate with your native query, so hibernate will not know about this statement and therefore it will not flush its data.
See also this answer of mine: https://stackoverflow.com/a/17889017/280244 about this topic

findAll method of DataService class returns only 100 entities

We've successfully migrated our v2 QBO to v3 and after that on the production we got an issue from one of our customers. They have over 100 their customers in QBO account. And they want to copy them into our application. We implemented an import like this:
DataService service = getDataService(owner); // obtain DataService via Access Keys
List<com.intuit.ipp.data.Customer> customers =
service.findAll(new com.intuit.ipp.data.Customer());
for (com.intuit.ipp.data.Customer customer : customers) {
createCustomer(customer, owner); // this is our internal method to create
}
As mentioned in Class Library Reference - method findAll is a
Method to retrieve all records for the given entity.
But our customer is getting only first 100 entities (Customer's) from his QBO v3 account. And if he do the same import operation - he will get the same first 100 entities again. This method doesn't allow any pagination options.
So the question is, how to get all of the entities?
You should use Query endpoint with page filters.
The following query gets customers 1 - 10:
String query = select($(customer)).skip(0).take(10).generate();
Output - SELECT * FROM Customer STARTPOSITION 1 MAXRESULTS 10
The following query gets customers 21 - 25:
String query = select($(customer)).skip(20).take(10).generate();
Ref - https://developer.intuit.com/docs/0025_quickbooksapi/0055_devkits/0201_ipp_java_devkit_3.0/0011_query_filters#Pagination
finally
QueryResult queryResult = service.executeQuery(query);
Thanks
To fetch all of customers for a specified business, first you need to get their count, and then, as mentioned at the previous answer, get the customers by "take" method:
Integer customersTotal = service.executeQuery(selectCount(_customer).generate()).getTotalCount();
QueryResult queryResult = service.executeQuery(select($(_customer)).skip(0).take(customersTotal).generate());
List<? extends IEntity> entities = queryResult.getEntities();
for (IEntity entity : entities) {
if (entity instanceof com.intuit.ipp.data.Customer) {
createCustomer((com.intuit.ipp.data.Customer) entity, owner);
}
}

Categories

Resources