community,
i have a problem to count entities after a entitymanager#save in the same transaction. The problem is, that i get always an inexpected count. I would like to count the current rows of a entity after a entitymanager save, but the result is always the current row count + the saved entity (which are not commited yet). It looks like a classic dirty read issue.
For this reason i use Propagation.REQUIRES_NEW and Isolation.REPEATABLE_READ to count only the commited entities.
If I use a h2 database the result is the result which i expected, but if i use MYSQL there is another result.
Here some code snippets:
The service:
#Service
public class MyTestService {
#Autowired
private EntityRepository entityRepository;
#Transactional
public void doSomeLogic() {
entityRepository.save(new Entity());
final long count = count();
if (count != 0) {
throw new IllegalStateException("Entity count should be 0 but is " + count);
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
public long count() {
return entityRepository.count();
}
}
The repo:
public interface EntityRepository extends PagingAndSortingRepository<Entity, Long>, JpaSpecificationExecutor<Entity> {
// #Override
// #Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW,
// isolation = Isolation.REPEATABLE_READ)
// long count();
}
The test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class DemoApplicationTests {
#Autowired
private MyTestService myTestService;
#Test
public void testEntityCounterWithMultiThreads() {
myTestService.doSomeLogic();
}
}
If i uncomment the repository code, all works, but I don't understand the difference.
Anybody knows where is my thinking blunder?
Related
I tryng to perform an an unpdate within a UnitTest method with org.springframework.transaction.annotation.Transactional annotation, but it seems that the update doesn't work.
If I remove #Transactional annotation on the method, the update works succesfully but it is visible for all others tests too.
Could you please indicate to me where I'm wrong?
I need the update effective only within the method with the #Transactional annotation and not visible for all others methods.
I'm using
Srping boot v 2.6.6. to start the application. I use JPA and Oracle Data Base.
This is my repository class where I use native query.
#Repository
#Transactional
public interface EsercentiRepository extends JpaRepository<EsercentiEntity, Long> {
// Update
#Modifying
#Query(value="update esercenti set sslfl=:sslfl where id_conv=:idConv", nativeQuery=true)
public void updateSslFlagByIdViaQuery(#Param("idConv") long idConv, #Param("sslfl") String sslfl);
// select
#Query(value="select id_conv,c_code,vendor_id,pos_id,abi_code,funzioni,ds,pos_id_sia,rifmer3d,sslfl "
+ " from esercenti where id_conv=:idConv", nativeQuery=true)
public EsercentiEntity getEsercentiByIdConvViaQuery(#Param("idConv") long idConv);
}
This is my Service class.
#Service
#Transactional
public class EsercentiServices implements IEsercenti {
#Autowired
private EsercentiRepository esercentiRepository;
#Override
public void updateSslFlagByIdViaQuery(long idConv, String sslfl) throws Exception {
esercentiRepository.updateSslFlagByIdViaQuery(idConv, sslfl);
}
#Override
public EsercentiEntity getEsercentiByIdConvViaQuery(long idConv) throws Exception {
return esercentiRepository.getEsercentiByIdConvViaQuery(idConv);
}
}
And this is my SpringBootTest class located in the 'test' directory where I use Junit 5 to perform Functional tests.
#EnableTransactionManagement
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#TestMethodOrder(MethodOrderer.OrderAnnotation.class)
#ExtendWith(SpringExtension.class)
#SpringBootTest
class TestGpay extends BaseServiceTester {
#Autowired
private IEsercenti esercentiServices;
[..]
#Test
#Transactional
public void TestDirectAuthGpayWithPayload(TestInfo testInfo) {
try {
// [..... Some codes]
EsercentiEntity ese = esercentiServices.getEsercentiByIdConvViaQuery(7826L);
esercentiServices.updateSslFlagByIdViaQuery(7826L, "Y");
EsercentiEntity ese2 = esercentiServices.getEsercentiByIdConvViaQuery(7826L);
// Send the http request. I expect to find the data changed on DB as per above update, but it is not.
WebUtils.HttpResponse response = netsJsonClient(endPoint, "POST", jsonObjectRequest.toString(), merId, merIdKsig);
// If I cancel the #Transactoinal annotation on the method level, the update is ok,
// but it is a global update and not only related to this database session.
} catch (Exception e) {
e.printStackTrace();
closeDriver(driver);
fail("Exception on test case " + testInfo.getDisplayName() + " Full Error:" + e);
}
}
Thanks in advance.
I'm using spring-data-cassandra:3.1.9 and the properties looks like :
spring:
data:
cassandra:
keyspace-name: general_log
session-name: general_log
local-datacenter: datacenter1
schema-action: CREATE
Cassandra version: apache-cassandra-4.0.1
spring-boot: 2.4.7
spring-data-jpa: 2.4.9
spring-jdbc: 5.3.8
spring-orm: 5.3.8
My entity looks like:
#ApiModel(description = "Audit log")
#Entity
#Table(name = "audit_log")
#org.springframework.data.cassandra.core.mapping.Table("audit_log")
public class AuditLogPO implements Serializable {
#PrimaryKeyClass
public static class Id implements Serializable {
private static final long serialVersionUID = 1L;
#ApiModelProperty(value = "业务标识")
#Column(name = "business_key")
#PrimaryKeyColumn(ordinal = 1, ordering = Ordering.ASCENDING)
private String businessKey;
// setters & getters ...
}
#javax.persistence.Id
#PrimaryKey
#org.springframework.data.annotation.Id
#Transient
private Id id;
#ApiModelProperty(value = "业务分区")
#Column(name = "business_partition")
#org.springframework.data.cassandra.core.mapping.Column(value = "business_partition")
private String businessPartition;
// getters & setters ...
}
After running this application, table audit_log will not be created automatically.
Actually, after digging into the source code located in spring-data-cassandra:3.1.9, you can check the implementation:
org.springframework.data.cassandra.config.SessionFactoryFactoryBean#performSchemaAction
wich implementation as following:
protected void performSchemaAction() throws Exception {
boolean create = false;
boolean drop = DEFAULT_DROP_TABLES;
boolean dropUnused = DEFAULT_DROP_UNUSED_TABLES;
boolean ifNotExists = DEFAULT_CREATE_IF_NOT_EXISTS;
switch (this.schemaAction) {
case RECREATE_DROP_UNUSED:
dropUnused = true;
case RECREATE:
drop = true;
case CREATE_IF_NOT_EXISTS:
ifNotExists = SchemaAction.CREATE_IF_NOT_EXISTS.equals(this.schemaAction);
case CREATE:
create = true;
case NONE:
default:
// do nothing
}
if (create) {
createTables(drop, dropUnused, ifNotExists);
}
}
which means you have to assign CREATE to schemaAction if the table has never been created. And CREATE_IF_NOT_EXISTS dose not work.
Unfortunately, we've not done yet.
SessionFactoryFactoryBean#performSchemaAction will be invoked as expected, however tables are still not be created, why?
It is because Spring Data JPA will add entities in org.springframework.data.cassandra.repository.support.CassandraRepositoryFactoryBean#afterPropertiesSet(org.springframework.data.mapping.context.AbstractMappingContext#addPersistentEntity(org.springframework.data.util.TypeInformation<?>)). But performSchemaAction method will be invoked in SessionFactoryFactoryBean. And all of these two FactoryBeans do not have an order and we do not know who will be firstly invoked.
Which means if SessionFactoryFactoryBean#afterPropertiesSet has been invoked firstly, probably no Entity is already there. In this circumstance, no tables will be created automatically for sure.
And how to create these tables automatically?
One solution is that you can invoke SessionFactoryFactoryBean#performSchemaAction in a bean of ApplicationRunner manually.
First of all, let's create another class extends from SessionFactoryFactoryBean as:
public class ExecutableSessionFactoryFactoryBean extends SessionFactoryFactoryBean {
#Override
public void createTables(boolean drop, boolean dropUnused, boolean ifNotExists) throws Exception {
super.createTables(drop, dropUnused, ifNotExists);
}
}
Next we should override org.springframework.data.cassandra.config.AbstractCassandraConfiguration#cassandraSessionFactory as:
#Override
#Bean
#Primary
public SessionFactoryFactoryBean cassandraSessionFactory(CqlSession cqlSession) {
sessionFactoryFactoryBean = new ExecutableSessionFactoryFactoryBean();
// Initialize the CqlSession reference first since it is required, or must not be null!
sessionFactoryFactoryBean.setSession(cqlSession);
sessionFactoryFactoryBean.setConverter(requireBeanOfType(CassandraConverter.class));
sessionFactoryFactoryBean.setKeyspaceCleaner(keyspaceCleaner());
sessionFactoryFactoryBean.setKeyspacePopulator(keyspacePopulator());
sessionFactoryFactoryBean.setSchemaAction(getSchemaAction());
return sessionFactoryFactoryBean;
}
Now we can create an ApplicationRunner to perform the schema action:
#Bean
public ApplicationRunner autoCreateCassandraTablesRunner() {
return args -> {
if (SchemaAction.CREATE.name().equalsIgnoreCase(requireBeanOfType(CassandraProperties.class).getSchemaAction())) {
sessionFactoryFactoryBean.createTables(false, false, true);
}
};
}
please refer this doc https://docs.spring.io/spring-data/cassandra/docs/4.0.x/reference/html/#cassandra.schema-management.initializing.config
But you still need to create keyspace before excuting the following codes:
#Configuration
public class SessionFactoryInitializerConfiguration extends AbstractCassandraConfiguration {
#Bean
SessionFactoryInitializer sessionFactoryInitializer(SessionFactory sessionFactory) {
SessionFactoryInitializer initializer = new SessionFactoryInitializer();
initializer.setSessionFactory(sessionFactory);
ResourceKeyspacePopulator populator = new ResourceKeyspacePopulator();
populator.setSeparator(";");
populator.setScripts(new ClassPathResource("com/myapp/cql/db-schema.cql"));
initializer.setKeyspacePopulator(populator);
return initializer;
}
// ...
}
You can also specify this behavior in your application.yml:
spring:
data:
cassandra:
schema-action: create-if-not-exists
Although, you will need to create the keyspace (with appropriate data center / replication factor pairs) ahead of time.
I am trying to develop a class that runs at specific intervals and performs some DB modifications.
the code I have managed to run at a specific interval, retrieve records from the DB, but when I want to commit changes to the DB I get the following error.
WFLYEE0110: Failed to run scheduled task: javax.persistence.TransactionRequiredException: WFLYJPA0060: Transaction is required to perform this operation (either use a transaction or extended persistence context)
is #ApplicationScoped allowed to create transactions?
Thanks!
#ApplicationScoped
#ActivateRequestContext
public class TaskRunner {
#PersistenceContext(type = PersistenceContextType.EXTENDED)
EntityManager em;
#Resource private ManagedScheduledExecutorService scheduler;
private ScheduledFuture<?> TaskRunnerScheduler;
private boolean initialized = false;
private void init(#Observes #Initialized(ApplicationScoped.class) Object init) {
if (initialized) return;
initialized = true;
try {
// Execute at startup
TaskRunner = scheduler.schedule(this::runSchedule, getSchedule());
} catch (Throwable throwable) {
}
}
#Transactional
private void runSchedule() {
//retrieve db records
//make changes and commit
//sample
//em.persist(someEntity)
}
private Trigger getSchedule() {
return new Trigger() {
#Override
public Date getNextRunTime(LastExecution lastExecutionInfo, Date taskScheduledTime) {
return Date.from(
ZonedDateTime.now().withSecond(0).withNano(0).plusHours("4").toInstant());
}
#Override
public boolean skipRun(LastExecution lastExecutionInfo, Date scheduledRunTime)
{return false;}};
}
}
Transactions are started via Interceptors.
When you call a method of a bean from inside that bean, the method-call is not intercepted and no Transaction can get started.
You need another Bean and persist in there
#RequestScoped
#Transactional(value = TxType.REQUIRES_NEW)
public class SomeOtherBean{
#PersistenceContext(type = PersistenceContextType.EXTENDED)
EntityManager em;
public void doSomething(){
//retrieve db records
//make changes and commit
//sample
//em.persist(someEntity)
}
}
then you can Inject that bean in your TaskRunner
#ApplicationScoped
#ActivateRequestContext
public class TaskRunner {
#Inject
SomeOtherBean someBean;
...
private void runSchedule() {
someBean.doSomething()
}
}
I'm trying to develop e very simple app based on a running thread creating entities in a DB every second in JavaEE on a Glassfish4 Server.
I'm using an Automatic Timer, where I inject an EJB managing the persistence.
The Timer Service is the following one:
#Singleton
#LocalBean
#Startup
public class UpdateEJB {
#EJB
MeasureEJB measureEjb;
#Schedule(second = "*/1", minute = "*", hour = "*", persistent = false)
public void doWork() {
measureEjb.create(new Measure());
}
}
While the EJB is:
#Stateless
public class MeasureEJB {
#PersistenceContext(unitName = "smarthomePU")
private EntityManager em;
public Measure create (Measure _measure) {
em.persist(_measure);
return _measure;
}
}
The Entity
#Entity
public class Measure implements Serializable {
#Id
private String time;
private int[] temp;
private boolean[] water;
public int[] getTemp() {
return temp;
}
public void setTemp(int[] temp) {
this.temp = temp;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public boolean[] getWater() {
return water;
}
public void setWater(boolean[] water) {
this.water = water;
}
private static final long serialVersionUID = 1L;
public Measure() {
super();
}
public Measure(int[] _temp, boolean[] _water) {
temp = _temp;
water = _water;
time = "";
}
}
But when I try to use the MeasureEJB in the UpdateEJB the app stops with a NPE on the create method. What am I doing wrong?
From the code fragments above, I cannot see how EntityManager is injected and since your're talking about an NPE the problem may lie there.
Also avoid #Singleton(s) if you don't keep state in your beans. By default your public void doWork() is associated with a Write Lock automatically.
What is the best way to handle transactions in this environment?
I have a Transacao class, which has a collection of Transacao.
public class Transacao {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pai")
private List<Transacao> filhos;
}
I load this in JSF from a EJB, something like:
public class TransacaoBean {
#EJB
private TransacaoService transacaoService;
private void edit(Long id) {
this.transacao = transacaoService.findById(id);
}
}
although, if I want to get the collection of filhos, I have to do this:
public class TransacaoBean {
...
private void edit(Long id) {
this.transacao = transacaoService.findById(id);
log.info(this.transacao.getFilhos.size()); //this throws a LazyInitializationException
}
}
and I get an Exception.
What is the best way to have this loaded in my JSF? I'm considering creating a Filter and using USerTransaction to keep the transaction open for the request or fetching the filhos in my EJB. Is there a better solution to this, which one is better?
The fetch's default value of the #OneToMany is FetchType.LAZY.
You can set it FetchType.EAGER to use them in non-managed environment.
Or you can make another EJB or method for getting a list or just the size.
public class TransacaoService {
public Transacao findById(final long id) {
...
}
public long getFilhosSize(final long id) {
// SELECT f FROM Transacao AS t WHERE t.pai.id=:id
}
#PersistenceContext
private EntityManager entityManager;
}