A library to execute database commands in transactions without having to use annotations based on Hibernate libraries. No Spring!
Maven dependencies available on maven central search.maven.org
<dependency>
<groupId>com.momomo</groupId>
<artifactId>momomo.com.platform.db.base.transactional.Hibernate</artifactId>
<version>5.0.2</version>
</dependency>
<repository>
<id>maven-central</id>
<url>http://repo1.maven.org/maven2</url>
</repository>
Our other, highlighted repositories
-
momomo.com.platform.Core
Is essentially what makes the our the core of several of momomo.com's public releases and contains a bunch of Java utility. -
momomo.com.platform.Lambda
Contains a bunch offunctional interfaces
similar toRunnable
,Supplier
,Function
,BiFunction
,Consumer
...
and so forth all packed in a easily accessed and understood intuitive pattern that are used plenty in our libraries.Lambda.V1E
,Lambda.V2E
,Lambda.R1E
,Lambda.R2E
, ... -
momomo.com.platform.Obj
Intuitive library that makes it easier for you to return multiple well defined objects on the fly from any method, any time rather than being limited to the default maximum of one. -
momomo.com.platform.Nanotime
Allows for nanosecond time resolution when asking for time from Java Runtime in contrast withSystem.currentTimeMillis()
. -
momomo.com.platform.db.transactional.Spring
A library to execute database commands in transactions without having to use annotations based on Spring libraries.
On Lambda
What makes this possible or even a reality is the existence of momomo.com.platform.Lambda
which we've now had in our posession for over 10 years. There is nothing magical about it, yet something similar is lacking in presence in the Java world.
Similar libraries might have recently made the scene but not quite as powerful nor as intuitive, we think.
We finally released momomo.com.platform.Lambda
to actually be able to release momomo.com.platform.db.transactional.Hibernate
and momomo.com.platform.db.transactional.Spring
.
momomo.com.platform.Lambda
is the most used and important library internally and is the essence and most important factor of what this library does.
Sure we could have switched our lambdas to Supplier
, Consumer
and so forth, whatever those names mean, but to destroy our code just to make our code 100% Java organic? Not a great idea.
It is just a bunch of interface classes after all contained in one file, and nothing expensive for you to add on.
Most of the industry today relies on the use of Spring @Transactional(propagation = Propagation.REQUIRES_NEW)
, @Transactional(propagation = Propagation.REQUIRES)
and @Transactional(propagation = Propagation.SUPPORTS)
.
We did too and frequently run into several very common issues.
To get the transactional features using Spring annotations the code needs to be:
-
Placed into a method of its own. So code has to be extracted and pollute the outer class scope.
Further, the method will have to declare a bunch of parametnanoers for you to pass whatever you needed in the previous scope.
Parameter and method pollution. Not great. -
The method has to be
public
and so can not beprotected
orprivate
when that might be the appropiate choice.
Aprotected
andprivate
method tells your class, subclasses something and outer world something else. They are important as communicators. -
The method has to be invoked using Springs injected beans, so a class or 'service' can not even invoke its own method directly.
This is risky since someone might do the wrong thing, invoke the method, expect a certain behaviour and not get it. -
You need a spring managed bean or use them. You need to inject it whereever you need to get transactional. Spring will hijack and show you stacktraces that are a frequent time waster to parse. Nothing is clear about them. Your exception stacktraces look like a labyrinth and you spend time finding the tiny bug you have which is the most costly part of using Spring hands down since it hijacks your entire Java platform.
Now this to us, introduces a bunch of issues as code is difficult enough to organize well using the standard restrictions the Java
language has alone and is now affecting the way you can code further
by forcing code that lives perfectly well within a method to be extracted and then somehow find itself to a Spring bean rather than stay where it is most relevant, where it was, just because you needed some code run in a transaction.
The extraction can not be private
but also has to be made public
, and not only that, they can not be invoked directly unless invoked from an injected proxying
bean, so forget static
, and forget creating the instances yourself, separating logic into several classes.
Repeat this enough times, and your code starts to look like Chernobyl all because you need to code the Spring
way. Spring
allows you to only use a subset of Java to work well, rather than add to Java. It is therefore by definition, a lesser, and more limited version of Java. Start using Spring and all your code now has to use Spring to invoke anything.
But Spring is not all that bad, there is plenty of material on how to setup things using Spring, which makes it easy to setup most of the time but it is not the dream of any hardcore Java developer. You give up a lot of your Java powers to be on that ecosystem and once you start injecting or annotating, they got you hooked and they work hard to lock you into their eco system. So does Hibernate / RedHat / JBoss. They will eventually all sell you support.
Spring is a bit kinder in their code though where Hibernate by default has private
access on almost all of their code. That makes it hard to extend and/ord modify behaiviour, so Spring is not all that bad, but once you start injecting, good luck breaking loose! It took us years to do so and there is no good reason to be injecting stuff in the first place and the amount of restrictions that this entails is far too many to accept.
Our code, including the building of the SessionFactory
and/or EntityManager
as well as being able to run code in transactions can all be run from a static void main
.
Can your Spring code do that? static void main
only and run? 1 sec launch? A static void main
can startup in less than a half a second
within your editor, while the injected stuff Spring pulls requires a lot of orchestration, an initiator, scanning and what not before allowing you do anything.
Now, using Hibernate as is, is better according to us. However, not much flavour gets added. Spring actually adds a some neccessary functionality, and their better transaction / session manager.
Hibernates transaction manager
/ sessionfactory
is not great. Their thread
implementation severely lacking. But we fixed its shortcomings.
But most people do not even know how to setup Hibernate without Spring and without XML
. It is not an easy topic to find info about actually. But we did the implementation in our $SessionConfig.java
. It is not all that easy as you can see, nor is it easy to google anything on Hibernate and not get a Spring answer today.
Remember the Javascript answers on
StackOverflow.com
not long ago? They used to be alljQuery
related.
Creating transactions using Hibernate is possible but requires programmatic insight and is prone to repetition and redundancy which leads to more potential errors and mistakes.
Futher, we repeat the Hibernate
option thread
or actually org.hibernate.context.internal.ThreadLocalSessionContext
implementation is very simplictic in nature and severely limited in terms of capacity and we have provided our own tweaked implementations, with long and suggestive names:
-
$SessionConfigContextListRecommended
Prevents wrapping ofopenSession()
calls as well as tracks them frommomomo.com.db.$SessionFactory
by adding thenew session
on top of a stack. When thenew session
completes, we remove it and the one previously on top becomes the one to be used forcurrentSession()
/requireSession()
calls. -
$SessionConfigContextSingleCrazyInsane
Prevents wrapping ofopenSession()
calls as well as tracks them frommomomo.com.db.$SessionFactory
by terminating / rolling back any previously attachedsession
and eating away any exception possibly thrown as a result, logging it only instead of throwing the exception. -
$SessionConfigContextSingleCrazySane
Prevents wrapping ofopenSession()
calls as well as tracks them frommomomo.com.db.$SessionFactory
by terminating / rolling back any previously attachedsession
by rolling it back yet throwing any exception that might arise rather than eating it. -
$SessionConfigContextSingleCrazyLaxed
Prevents wrapping ofopenSession()
calls as well as tracks them frommomomo.com.db.$SessionFactory
by detaching previously trackedsession
silently, not terminating them but allowing them to continue to live, in hopes that the developer takes responsibility of manually terminating them instead. -
$SessionConfigContextUntrackedLeastRecommended
Prevents wrapping ofopenSession()
calls but does not trackopenSession()
calls like the other ones do because it uses Hibernates ownThreadLocalSessionContext
where we basicaly just overrideThreadLocalSessionContext
to prevent the wrapping of theSession
upon a call tocurrentSesssion()
which for some reason by default returns a proxied, wrapped and limited dumb proofSession
that's lacking on so many levels.
Â
Basically it will behave almost identical to$SessionConfigContextSingleCrazyInsane
but here we ensure that the implementation used is Hibernates own but with two difference. 1) We only override their method to prevent wrapping. 2)$SessionConfigContextSingleCrazyInsane
is tracked frommomomo.com.db.$SessionFactory
. This one is not.
It is an easy to use, annotation free, Transactional API
, that offers a vast amount of methods to create, or reuse transactions by either delegating to existing implementations in the case of Spring
or add a bit of additional flavour in the case of Hibernate
only version to match Spring's
transaction capabilities.
It is a fully customizable, flexible, overridable, optionable and all the other able thing the world has to offer. Simply put, it is very capable.
What we've done is written a base momomo.com.platform.db.transactional
which is then currently implemented with two flavors
-
momomo.com.platform.db.transactional.Spring
For those currently stuck or happy with Spring.
It makes use of SpringsPlatformTransactionManager
as the tool to perform your transactions using our API which basically setups the transaction and then delegates it to Spring to make the transaction work as you intended. Â
This works great and have been tested extensively as this library came before we switched to our now Hibernate only library to get off the injection, beans and annotation trail. -
momomo.com.platform.db.transactional.Hibernate
Our camp and our recommendation to all new adopters, and for those that do not want to useSpring
.We broke loose from Spring setting up our
EntityManagerFactory
which in reality just wraps a HibernateSessionFactory
as well as broke loose from relying onSpringPlatformTransactionManager
by basically writing our own transaction manager (not that difficult) usingHibernate
Session
transaction mechanisms. -
Coming soon and already in a somewhat acceptable condition is the JDBC only transactional API. It already provides similar mechanisms, but is not fully developed as the other two.
Seemomomo.com.db.$DatabaseTransactional
for the current state of the JDBC version.
It is not a tit for tat implementation, but gives us a limited transactional API, that is similar.
What we get is a unified Transactional API
that can be setup and invoked from anyplace, and you could one day even switch from Spring
to Hibernate
and retain your functionality.
Not at all. We also provide a bunch of other things, such as the simplicy of setting up your hibernate SessionFactory
, as well as Migrations
(think liquibase, flyway), as well as a bunch of
session related utility related to entities, persisting, finding, saving, building and more.
All of this can execute from a static void main
. Zero xml. Zero complexity.
The entire thing is really much much simpler to understand and navigate if you just check out the entire repository, navigate and/or run the examples. But we will try to guide you here as well.
We've decided to develop a Crypto related application!
Start by looking at
-
Crypto.java
Contains code for setting up the postgresqldatabase
and the HibernateSessionFactory
as well as provides aCryptoTransactional
andCryptoRepository
which will be used in our examples later and is the implementation used in our running examples.Objects are created and separated intentionally to show you the different areas of responsibility.
-
CryptoMinimal.java
is very similar toCrypto
but is densed downed to show you what the minimal working configuration would actually look like. -
CryptoLargest.java
is very similar toCrypto
but contains more examples and comments on some things we can modify when setting up ourdatabase
and theSessionFactory
.
// This is all code required to get started!
public class CryptoMinimal {
/////////////////////////////////////////////////////////////////////
private static final SessionFactory SESSION_FACTORY = new CryptoSessionConfig().create();
public static final CryptoTransactionalRepository REPOSITORY = new CryptoTransactionalRepository();
/////////////////////////////////////////////////////////////////////
public static final class CryptoDatabase implements $DatabasePostgres {
@Override public String name() {
return "crypto_database_name_based_on_hibernate_libraries";
}
@Override public String password() {
return "postgres";
}
}
/////////////////////////////////////////////////////////////////////
public static final class CryptoSessionConfig extends $SessionConfig<CryptoDatabase> {
private CryptoSessionConfig() {
super(new CryptoDatabase());
}
@Override protected String[] packages() {
return new String[]{ "momomo/com/example/app/entities" }; // The package to scan for entities
}
}
/////////////////////////////////////////////////////////////////////
/**
* Note, both a repository and a transactional instance class in one!
*/
public static final class CryptoTransactionalRepository implements $SessionManagerRepository, $TransactionalHibernate {
@Override public SessionFactory sessionFactory() {
return SESSION_FACTORY;
}
}
/////////////////////////////////////////////////////////////////////
}
Now that you've seen it, glanced it, consumed it, as well as having glanced Crypto
which is the one we actually use, you may proceed.
Link to $TransactionalHibernate
and for the lazy, here are the method signatures in $Transactional
:
Part 1
- Bitcoin.java
We start by looking at our first entity Bitcoin
@Entity ... public ... final class Bitcoin implements $Entity {
@Id
private UUID id;
private Timestamp time;
private double usd; // Represents the price in usd
...
public static final Service S = new Service(); public static final class Service {
/**
* Insert method that creates a bitcoin, then requires a transaction, and then saves it within the transaction, which then commits if it was the one to start it.
*/
public Bitcoin insert(Timestamp time, double usd) {
// This "very very expensive" creation need not to run inside the transaction
Bitcoin entity = new Bitcoin().setId(UUID.randomUUID()).setTime(time).setUsd(usd);
// Now, require the transaction and execute save within, also return the saved entity.
return Crypto.repository.requireTransaction(() -> {
return Crypto.repository.save(entity);
});
}
}
In method insert()
we have
// We create the entity
Bitcoin entity = new Bitcoin()
.setId(UUID.randomUUID())
.setTime(time)
.setUsd(usd)
;
// Then we require a write capable 'transaction' which means create a 'new transaction'
// if there is not already an ongoing one for this thread
return Crypto.repository.requireTransaction((tx) -> {
return Crypto.repository.save(entity);
});
The save(entity)
call will execute within a transaction and when it terminates it will commit transaction if it was the one who started it.
The save()
call could really be your own normal logic. If you use Spring
you would use whatever you where you using before, likely using an EntityManager
and calling em.save(entity)
, and if Hibernate
likey session.saveOrUpdate(entity)
.
Here we used our already created and capable repository which will eventually call session.saveOrUpdate(entity)
and to ensure that it was saved properly, it checks it was assigned an id
as it should which is not always the case.
When using our save()
we will also check to see if the entity has implemented $Entity.Events
where your entity may implement beforeSave()
and/or afterSave()
logic which will be triggered before and/or after a save()
. We use this sometimes to set default values* on some fields or generate values based on other fields to set a third.
Note!
To be able to use our repository we currently require that your
entities
implement our empty$Entity
interface.We can see that this is no longer really required as the interface is indeed empty but this was a safety mechanism for our internal code once to ensure control as means of declaration but we will eventually remove this requirement from our
Repository
implementation to support any entity.The Transactional API does not have that requirement, but the
repository.save(..)
,repository.find(...)
currently do.
We can now invoke
Bitcoin.S.insert(Time.stamp(), 10000.1)
from anyplace, even from a plain static void main
.
So given the following in Bitcoin.Service
:
public Bitcoin insert(Timestamp time, double usd) {
Bitcoin entity = new Bitcoin().setId(Randoms.UUID()).setTime(time).setUsd(usd);
return Crypto.repository.requireTransaction((tx)-> {
return save(entity);
});
}
public void populate(int multiply) {
// Multiple transactions each started inside the insert method
insert(Time.stamp(), multiply * 1);
insert(Time.stamp(), multiply * 2);
// Two at once, insert call will just continue using this created transaction
Crypto.repository.requireTransaction(() -> {
insert(Time.stamp(), multiply * 3);
insert(Time.stamp(), multiply * 4);
});
}
we can execute
public static void main(String[] args) {
Bitcoin.S.populate();
}
which will trigger the database generation, scan for entity classes in the configured packages, setup the SessionFactory
and get you a transaction to eventually create and save entities to the database.
Part 2
- Polkadot.java
You saw requireTransaction(()->{ ... })
in Bitcoin
but let us see what else we can do.
First however, we rewrite Bitcoin
class by making it prettier by utilitizing the already created inner class Crypto.CryptoService
at the bottom of Crypto
.
The implementation is minimal, simple and straightforward to declare and use.
public static abstract class CryptoService<T extends $EntityId> extends $Service<T> implements $TransactionalHibernate {
@Override public CryptoRepository repository() { return CRYPTO.REPOSITORY; }
}
We make use of this class in a another class Polkadot
that now contains a minimal version of the logic found in Bitcoin
with the following changes listed:
- The entity class now extends
$EntityIdUUID
which will generate an UUID identifier for us, so need to manually set that part. Polkadot.Service
nowextends
Crypto.CryptoService<Polkadot>
which will gives access to a bunch of methods, such assave(..)
,list()
,validate()
,findByField(...)
,findByEntity(...)
,reqireTransaction(...)
,newTransaction(...)
,supportTransaction(...)
and many more without the need for external reference for access like inBitcoin
where we didCrypto.repository.requireTransaction(...)
, now we can simply dorequireTransaction(...)
.
// This you saw in Bitcoin.java
Crypto.repository.requireTransaction(() -> {
return Crypto.repository.save(entity);
});
// This is what we now do through inheritance now
requireTransaction(() -> {
return save(entity);
});
Part 3
- Etherum.java
Let us now take a look at sample code found in our dummy class Etherum.Service
which extends
Crypto.CryptoService<Etherum>
containing just example code that is really never invoked.
// Given
Etherum entity = new Etherum()
.setId(UUID.randomUUID())
.setTime(time)
.setUsd(usd)
;
// We can demand a new transaction regardless of an existing one using
requireTransaction(() -> {
return save(entity);
});
// A read only transaction
List<Polkadot> = supportTransaction(() -> {
return list(); // super.list() available from the super class $Service
});
// We can disable auto commit
requireTransaction(($TransactionHibernate transaction) -> {
// Disable autocommit, so we commit when we want or not at all
transaction.autocommit(false);
save(entity);
transaction.commit();
});
// Another way to disable automatic commit
requireTransaction((tx) -> {
save(entity);
tx.commit();
}, false /** commit false**/ );
// We can hook in to do something once the commit is succesful
requireTransaction((tx) -> {
tx.afterCommit(() -> {
// Send email perhaps when we exit the transaction after succesfully committing!
// We have now inserted the value in our database successfully!
// We do not want to send the email unless we actually have 100% inserted the stuff so this is a handy method to use for that
});
save(entity);
});
// Rolling back inside a lambda possible
newTransaction((tx) -> {
save(entity);
tx.rollback();
// note, now, the autocommit won't occur, as we have rolled back and basically discarded the entire transaction.
});
// We can return something from inside the transaction, like an entity
Etherum e = requireTransaction(() -> {
return save(entity); // We repeat the return demo but by returning an entity
});
// Return again, showcasing that you can return anything really
String returns = requireTransaction(() -> {
save(entity);
return "we can return anything from the lambda";
});
// A lambda less example, if we do not want to execute things inside a lambda but desire more freedom?
$TransactionHibernate tx = requireTransaction();
save(entity);
tx.autocommit(false);
tx.afterCommit (()-> {});
tx.afterRollback(()-> { /* A crime has been committed! Report error to the FBI! */ });
tx.rollback();
tx.commit();
...
// We show that exceptions will bubble up to the caller
try {
requireTransaction(() -> { throw new IOException(); });
} catch (IOException exception) {
// Will bubble the exception to the caller (due to Lambda.VE, Lambda.V1E) after rolling back.
// If there is a rollback exception, a $DatabaseRollbackException will be thrown instead.
// Will not commit.
}
// We can return and throw exceptions
try {
File file = requireTransaction(() -> {
if ( true ) { return new File(""); }
throw new IOException();
});
} catch (IOException exception) {
// Will bubble the exception to the caller (due to Lambda.VE, Lambda.V1E) after rolling back.
// If there is a rollback exception, a $DatabaseRollbackException will be thrown instead.
// Will not commit.
}
// We can get access to the actual `session` and retain 100% control
Session s1 = requireSession();
Session s2 = newSession();
// We can build the transaction properties and set various things ourselves
requireOptions()
.propagation(Propagation.NEW)
.isolation(Isolation.REPEATABLE_READ)
.timeout(1000)
.create()
.execute((tx)-> {
tx.autocommit(false);
tx.afterCommit(()-> {});
tx.afterRollback(()-> {});
// ...
});
// Similar to previous example but without chaining
$TransactionOptionsHibernate options = requireOptions();
// ... options.propagation(...)
// ... options.create()
// Getting a transaction that we can execute
$TransactionHibernate tx = requireOptions()
.propagation(Propagation.NEW)
.isolation(Isolation.REPEATABLE_READ)
.timeout(1000)
.create()
;
tx.execute(()-> {
save(entity);
});
tx.execute(()-> {
save(entity);
tx1.commit();
}, false /** don't commit **/ );
// More options made visible
requireOptions()
.timeout(1000)
// Notice the withConnection option being used! Full access!
.withConnection((java.sql.Connection connection) -> {
connection.setReadOnly(true);
connection.setCatalog("catalog");
connection.setTransactionIsolation(1);
connection.clearWarnings();
connection.createStatement();
connection.setTypeMap(new HashMap<>());
connection.setHoldability(1);
connection.setSavepoint();
// ...
})
.create()
.execute(tx -> {
tx.autocommit(false);
save(entity);
tx.commit();
})
;
Part 4
- Stellar.java
While in Bitcoin
, Etherum
, Polkadot
services, we required the transaction inside the insert method, in Stellar
, we no longer make use of requireTransaction()
there because we figured it was better design to place that burden on the caller to know the call needs a transaction to reduce boiler plate further.
Using Spring
, the caller would have had to extract those parts to fit neatly into a method while the caller for us would simply wrap them inside a lambda.
So rather than doing:
public Stellar insert(Timestamp time, double usd) {
return requireTrasaction( () -> save( create().setTime(time).setUsd(usd) ) );
}
in Stellar.Service
we simply do:
public Stellar insert(Timestamp time, double usd) {
return save( create().setTime(time).setUsd(usd) );
}
Doing this we expect the caller to do the requireTransaction()
call for us.
In Stellar.Service
we can also find more complex working example:
/**
* Bunch of complex and nested transactions.
*
* Take a look at the comments within!
*/
public void populate(int multiplier) {
newTransaction((tx1) -> {
insert(Time.stamp(), 11);
insert(Time.stamp(), 12);
// Start a new transaction within
newTransaction(tx2 -> {
insert(Time.stamp(), -21);
insert(Time.stamp(), -22);
// Another one
newTransaction(tx3 -> {
insert(Time.stamp(), 31);
insert(Time.stamp(), 32);
});
// Continue on the previous last active one (same as tx2)
requireTransaction(tx4 -> {
insert(Time.stamp(), -41);
insert(Time.stamp(), -42);
});
// Neither -21 ... or -41 ... will get into db since that tx rolledback
tx2.rollback();
// The same as tx1, no issues there. Should be in.
requireTransaction(tx5 -> {
insert(Time.stamp(), 51);
insert(Time.stamp(), 52);
// The same as tx1 and tx5, no issues there. Should enter db.
requireTransaction(($TransactionHibernate tx6) -> {
insert(Time.stamp(), 61);
insert(Time.stamp(), 62);
// New transaction, will be in
newTransaction(($TransactionHibernate tx7) -> {
insert(Time.stamp(), 71);
insert(Time.stamp(), 72);
});
// New transaction, won't be in since it is rolled back
newTransaction(($TransactionHibernate tx8) -> {
insert(Time.stamp(), -81);
insert(Time.stamp(), -82);
tx8.rollback();
});
});
});
});
});
}
We also added a couple more methods, just as examples on what else we can do, although never invoked:
// Return all the historic data within polkadot table
public List<Stellar> historic() {
return supportTransaction(()-> {
return super.list();
});
}
// Return the data within time range
public List<Stellar> range(Timestamp from, Timestamp to) {
return supportTransaction(()-> {
return list(criteria()
.add( Restrictions.ge(Cons.time, from) )
.add( Restrictions.le(Cons.time, to ) )
);
});
}
If we now look at PUBLIC_STATIC_VOID_MAIN
we can find a static void main
and some code ready to run the entire thing.
public static void main(String[] args) {
Bitcoin.S.populate(1);
Polkadot.S.populate(1);
Stellar.S.populate(1);
// We disable autocommit using false, and commit manually
{
Crypto.repository.requireTransaction(tx-> {
Bitcoin.S.populate(1000);
tx.commit();
}, false /** disable autocommit **/);
}
// We rollback from inside the lambda
{
Crypto.repository.requireTransaction(tx -> {
Bitcoin.S.populate(-10000);
tx.rollback();
});
}
// We rollback from 'free' mode
{
$Transaction tx = Crypto.repository.requireTransaction();
Bitcoin.S.populate(-100000);
tx.rollback();
}
}
When we run this static void main we will eventually find the following in our database:
The full license can be found here MoL9
Send an email to opensource{at}momomo.com
if you would like to contribute in any way, make changes or otherwise have thoughts and/or ideas on things to improve.