Why is not completed transaction in Spring boot on TransactionStatus? - java

I don't know why is transaction is not completed.
Example)
public void transactionMethod() {
DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus testTx = txManager.getTransaction(txDefinition);
try {
txManager.commit(testTx);
} catch (Exception ex) {
log.error("Catch!!!!!!!!!!!");
}
if (Objects.nonNull(testTx) && !testTx.isCompleted()) {
txManager.rollback(testTx);
}
}
#Test
public void test() {
SimpleTransactionStatus testTx = new SimpleTransactionStatus();
when(txManager.getTransaction(any())).thenReturn(testTx);
target.transactionMethod();
}
I think rollback is not implemented because commit ended in try{}.
However, testTx.isCompleted()is false.....
Could you tell me why testTx.isCompleted() is false?

Related

If I started transaction but because of the condition, I shouldn't do this, should I use commit or can I call rollback right away?

I have Dao Service that make order and If I don't found a car I don't need to commit
#Override
public String makeOrder(String[] stingNumbers, String[] categories, String userAddress, String userDestination, String login) {
int[] numbers = Stream.of(stingNumbers).mapToInt(Integer::parseInt).toArray();
Car[] foundCars = new Car[categories.length];
String messageTakenTime = null;
MySQLDAOFactory.createConnectionScope();
MySQLDAOFactory.createTransaction();
User foundUser = userDao.findUser(login);
for (int i = 0; i < foundCars.length; i++) {
foundCars[i] = carDao.findCar(numbers[i], categories[i]);
if (foundCars[i] == null) {
MySQLDAOFactory.endTransaction();
MySQLDAOFactory.abortTransaction();
MySQLDAOFactory.endConnectionScope();
return String.format("false %s %d", categories[i], numbers[i]);
}
carDao.updateCar(foundCars[i].getCarId(), "on Order");
double distance = DistanceUtil.getDistance(userAddress, userDestination);
CarCategory foundCarCategory = categoryDao.findCarCategory(categories[i]);
double discount = foundCarCategory.getDiscount();
double costPerKilo = foundCarCategory.getCostPerOneKilometer();
int scale = (int) Math.pow(10, 1);
double orderCost = (double) Math.round((distance * costPerKilo) - ((distance * costPerKilo) * discount) * scale) / scale;
Order order = new Order();
order.setUserId(foundUser.getUserId());
order.setCarId(foundCars[i].getCarId());
order.setOrderDate(LocalDateTime.now());
order.setUserAddress(userAddress);
order.setUserDestination(userDestination);
order.setOrderCost(orderCost);
orderDao.insertOrder(order);
if (messageTakenTime == null) {
messageTakenTime = DistanceUtil.takenTime(distance);
}
}
MySQLDAOFactory.endTransaction();
MySQLDAOFactory.endConnectionScope();
return messageTakenTime;
}
I have methods in DaoFactory that working with connection(open connection, start transaction,close connection,close transaction and make rollback)
public static void createTransaction() {
isTransaction = true;
try {
connection.setAutoCommit(false);
} catch (SQLException throwables) {
LOGGER.error(throwables);
}
}
public static void endTransaction() {
try {
connection.commit();
} catch (SQLException throwables) {
LOGGER.error(throwables);
}
}
public static void abortTransaction() {
try {
connection.rollback();
} catch (SQLException throwables) {
LOGGER.error(throwables);
}
}
public static void createConnectionScope() {
isConnectionScope = true;
try {
connection = DATA_SOURCE.getConnection();
} catch (SQLException e) {
LOGGER.error(e);
}
}
public static void endConnectionScope() {
isConnectionScope = false;
try {
connection.close();
} catch (SQLException throwables) {
LOGGER.error(throwables);
}
}
In my example If i dont want to commit my transaction what should I do?Call rollback?Or call commit and after that rollback? Also, if you could tell me how to catch exceptions in these methods, or let the method throw them and catch them directly in the service layer, because I don't quite understand how it is done. Thanks for reply.
If you don't want to commit a transaction you need use ROLLBACK.
If you COMMIT the transaction there is nothing left to ROLLBACK, these operations are mutually exclusive.
These operations that happens should be catch wherever are the logic of your application. Some times after a exception happen you need to do more things than a ROLLBACK. If you capture the exception in your DAO class you can't tell exactly what was happening and generate better messages or specific logic.

Hibernate have to wait for a few seconds before I can get the object that I just created

With this session handler:
public class SessionHandler {
private static SessionFactory DBContext;
static {
try {
DBContext = HibnerateConfiguration.config().buildSessionFactory();
}
catch(Throwable t) {
throw new ExceptionInInitializerError(t);
}
}
/*
* Returns a session anyway. If currently no session exist, open a new one;
* If there is a current session, use the existing one.
*/
#Override
public Session getSession() {
try {
return DBContext.getCurrentSession();
}
catch (HibernateException he) {
logger.error("session already exist.");
return DBContext.getCurrentSession();
}
}
public void close() {
DBContext.close();
}
}
and the following create and get methods:
public Serializable create(T type_entity) {
Session session = getSessionHandler().getSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Serializable result = session.save(type_entity);
tx.commit();
return result;
} catch (HibernateException ex) {
ex.printStackTrace();
if (tx!=null) tx.rollback();
throw ex;
} finally {
getSessionHandler().close();
}
}
#SuppressWarnings("unchecked")
public T get(Serializable id) throws InvalidRequestException {
Session session = getSessionHandler().getSession();
Transaction tx = session.beginTransaction();
tx.commit();
try {
Object obj = session.get(_classtype, id);
if (obj == null) {
throw new InvalidRequestException(String.format("requested object with id %s does not exist.", id));
} else {
return (T)obj;
}
} catch(HibernateException ex) {
ex.printStackTrace();
if (tx!=null) tx.rollback();
throw ex;
} finally {
getSessionHandler().close();
}
}
When I create an object that returns me id = 4, and if immediately I make a request on browser that eventually ask for the new object of id 4, I have to wait for a few seconds (last time I tried is > 3 seconds).
When the id is returned from the create, the data should already exist. However the get returns null. I highly suspect the get is using the old cache which then is updated every a few seconds, but I have no idea how to fix it.
Let me know if any info is required and I am happy to provide them.

Strange behaviour with ConnectionFactory using CDI with Tomcat 7

I have this class (my database connection factory):
#ApplicationScoped
public class ConnectionFactory {
private ComboPooledDataSource datasource;
private Long open = 0l;
private Long close = 0l;
#PostConstruct
public void init() throws PropertyVetoException, SQLException {
datasource = new ComboPooledDataSource();
datasource.setDriverClass("org.postgresql.Driver");
datasource.setJdbcUrl("jdbc:postgresql:dbcampanha");
datasource.setUser("postgres");
datasource.setPassword("admin");
datasource.setMinPoolSize(1);
datasource.setMaxPoolSize(5);
datasource.setCheckoutTimeout(30000);
datasource.setUnreturnedConnectionTimeout(30);
datasource.setMaxIdleTime(30);
datasource.setDebugUnreturnedConnectionStackTraces(true);
datasource.setAcquireIncrement(1);
}
#Produces
#RequestScoped
public Connection getConnection() throws ClassNotFoundException {
open++;
try {
Connection connection = datasource.getConnection();
connection.setAutoCommit(false);
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void close(#Disposes Connection connection) {
close++;
try {
connection.commit();
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public Long getOpenedConnectionCounter() {
return open;
}
public Long getClosedConnectionCounter(){
return close;
}
public ComboPooledDataSource getDatasource(){
return datasource;
}
}
I use this class with an JAX-RS application. And for some tests using this route:
#RequestScoped
#Path("/test")
public class TesteService {
#Inject
private Connection connection;
#GET
#Produces(MyMediaType.JSON)
#Path("/yes")
public Response success() throws SQLException {
connection.getClientInfo("");
return Response.ok().build();
}
}
And this class for my Client:
public class TesteMain {
private static final String prefix = "http://localhost:8080/schoolwork/service/test/";
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000; i++) {
Request request = new Request(prefix + "yes");
request.start();
if(i % 10 == 0)
Thread.sleep(1000l);
}
}
public static class Request extends Thread {
private String rota;
public Request(String rota){
this.rota = rota;
}
#Override
public void run() {
try {
HttpURLConnection url = (HttpURLConnection) (new URL(rota).openConnection());
url.connect();
System.out.println(url.getResponseCode() == 200 ? "SUCCESS" : "ERROR");
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
I receive this infos:
{
"opened-connection": 789,
"closed-connection": 867,
}
Yes, I have a number of closed database connection greater than opened. HOW? Any idea for this?
I use
Tomcat 7 + Java 7
P.S. I am sorry for my bad English :/
SOLVED
I change my counters for AtomicInteger objects, and works perfectly.
So, two quick comments:
Your counters are (boxed) longs read and updated willy-nilly by 1000 concurrent Threads. Their values will in general be unpredictable and nondeterministic. They certainly won't accurately count what you intend them to count. Consider using atomic operations on AtomicLongs instead.
Your commit() (or rollback()) should be attached to your database business logic, the part where you can tell a unit of work has either succeeded or failed. You shouldn't automatically commit on close.

How to test this code effectively?

My apologies for throwing this random subject, but I did not come up with a better name,
class ReportSenderRunnable implements Runnable {
private final LPLogCompressor compressor;
public ReportSenderRunnable(final LPLogCompressor compressor) {
this.compressor = compressor;
}
#Override
public void run() {
executeTasks();
}
private void executeTasks() {
try {
// compressor.compress();
reportStatus = ReportStatus.COMPRESSING;
System.out.println("compressing for 10 seconds");
Thread.sleep(10000);
} catch (final IllegalStateException e) {
logCompressionError(e.getMessage());
} /*catch (final IOException e) {
logCompressionError(e.getMessage());
}*/ catch (InterruptedException e) {
logCompressionError(e.getMessage());
}
try {
reportStatus = ReportStatus.SENDING;
System.out.println("sending for 10 seconds");
Thread.sleep(10000);
} catch (final InterruptedException e) {
reportStatus = ReportStatus.EXCEPTION_IN_SENDING;
}
try {
reportStatus = ReportStatus.SUBMITTING_REPORT;
System.out.println("submitting report for 10 seconds");
Thread.sleep(10000);
} catch (final InterruptedException e) {
reportStatus = ReportStatus.EXCEPTION_IN_SUBMITTING_REPORT;
}
System.out.println("Report Sender completed");
reportStatus = ReportStatus.DONE;
}
private void logCompressionError(final String cause) {
logError(ReportStatus.COMPRESSING, cause);
reportStatus = ReportStatus.EXCEPTION_IN_COMPRESSION;
}
private void logError(final ReportStatus status, final String cause) {
LOGGER.error("{} - {}", status, cause);
}
}
Ideally, statements like
System.out.println("sending for 10 seconds");
Thread.sleep(10000);
will be replaced by actual tasks, but for now assuming this is the case, and they way it runs is
private void submitJob() {
final ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
final LPLogCompressor lpLogCompressor = getLpLogCompressor();
executorService.execute(getReportSenderRunnable(lpLogCompressor));
} catch (final IOException e) {
reportStatus = ReportStatus.EXCEPTION_IN_COMPRESSION;
LOGGER.debug("Error in starting compression: {}", e.getMessage());
}
System.out.println("started Report Sender Job");
}
My question was how to effectively test this code? The one I wrote is
#Test
public void testJobAllStages() throws InterruptedException, IOException {
final ReportSender reportSender = spy(new ReportSender());
doReturn(compressor).when(reportSender).getLpLogCompressor();
when(compressor.compress()).thenReturn("nothing");
reportSender.sendAndReturnStatus();
Thread.sleep(10);
assertEquals(ReportStatus.COMPRESSING, reportSender.getCurrentStatus());
Thread.sleep(10000);
assertEquals(ReportStatus.SENDING, reportSender.getCurrentStatus());
Thread.sleep(10000);
assertEquals(ReportStatus.SUBMITTING_REPORT, reportSender.getCurrentStatus());
}
This runs well for above code.
To me this is crappy for following reasons
Not all tasks would take same time in ideal cases
Testing with Thread.sleep will take too much time and also adds non-determinism.
Question
How do I test this effectively?
You could add a class with a method (e.g., TimedAssertion.waitForCallable) that accepts a Callable, which then uses an ExecutorService to execute that Callable every second until it returns true. If it doesn't return true in a specific period of time, it fails.
You would then call that class from your test like this:
boolean result;
result = new TimedAssertion().waitForCallable(() ->
reportSender.getCurrentStatus() == ReportStatus.COMPRESSING);
assertTrue(result);
result = new TimedAssertion().waitForCallable(() ->
reportSender.getCurrentStatus() == ReportStatus.SENDING);
assertTrue(result);
...etc. This way, you can easily wait for a particular state in your code to be true, without waiting too long -- and you can reuse this new class anywhere that you need this sort of assertion.
Based on #Boris the Spider comment, I made use of mocks and here is what my tests look like
#Mock
private ReportSenderRunnable reportSenderRunnable;
#Mock
private LPLogCompressor compressor;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test(timeout = 1000)
public void testJobNoException() throws InterruptedException, IOException {
final ReportSender reportSender = spy(new ReportSender());
doReturn(compressor).when(reportSender).getLpLogCompressor();
when(compressor.compress()).thenReturn("nothing");
reportSender.sendAndReturnStatus();
Thread.sleep(10);
assertEquals("Job must be completed successfully", ReportStatus.DONE,
reportSender.getCurrentStatus());
}
#Test(timeout = 1000)
public void testJobWithIllegalStateException() throws Exception {
final ReportSender reportSender = spy(new ReportSender());
doReturn(compressor).when(reportSender).getLpLogCompressor();
doThrow(IllegalStateException.class).when(compressor).compress();
reportSender.sendAndReturnStatus();
Thread.sleep(10);
assertEquals("Job must failed during compression", ReportStatus.EXCEPTION_IN_COMPRESSION,
reportSender.getCurrentStatus());
}

Spring Transaction Rollback When No SQL is Executed

I am wondering if I need to rollback the following if the orderId is null, and the following SQL statement is never run? This is a simplified example of a method that is somewhat larger.
I am wondering if starting a transaction, but not doing anything due to the orderId being null, should be terminated somehow.
public OrderInfo insertOrder(String orderId) throws OrderException {
OrderInfo orderInfo = null;
DefaultTransactionDefinition txnDefinition = new DefaultTransactionDefinition();
txnDefinition.setName("InsertOrder");
txnDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus txnStatus = transactionManager.getTransaction(txnDefinition);
if (orderId != null && !orderId.isEmpty()) {
try {
orderInfo = orderDao.insertOrder(orderId);
if (orderInfo != null && orderInfo.getOrderId() > 0) {
transactionManager.commit(txnStatus);
}
else {
transactionManager.rollback(txnStatus);
throw new OrderException();
}
} catch (Exception e) {
transactionManager.rollback(txnStatus);
throw new OrderException();
}
}
// else rollback?
return orderInfo;
}
I suggest to try Spring TransactionTemplate
TransactionTemplate tt = new TransactionTemplate(transactionManager, transactionDefinition);
OrderInfo orderInfo = tt.execute(new TransactionCallback<OrderInfo>() {
#Override
public T doInTransaction(TransactionStatus status) {
return orderDao.insertOrder(orderId);
}
});
this is a guarantee that all things will be done right

Categories