I try to define immutable Event-Entites (Event Sourcing) using Java JPA/Hibernate and want those events to have an absolute ordering that already is defined right after object-creation, before any persistence has taken place (no distributed setup where one would need consensus)
We are using automatic auditing properties using #CreatedDate but I crossed that off my list since this is only populated during persistence.
I can think of 2 options:
using a global database-sequence that is queried during object-creation
Long ordering = System.nanoTime()
any advice/idea appreciated.
want those events to have an absolute ordering that already is defined
right after object-creation
The simpliest solution is like:
public class SomeEntity {
private LocalDateTime createdAt = LocalDateTime.now();
}
Of course it is possible that two objects will be created simultaneously and will have the same date, but it might be extremely hard to get.
A bit more complex - ordered by id if dates are equal and both objects are persisted. There may be indeterminacy if some of objects is not persisted yet, but if both are persisted - strict order is guaranteed:
public class SomeEntity implements Comparable<SomeEntity> {
private Long id;
private LocalDateTime createdAt = LocalDateTime.now();
#Override
public int compareTo(SomeEntity o) {
int result = createdAt.compareTo(o.createdAt);
if (result == 0) {
if (id != null && o.id != null) {
result = id.compareTo(o.id);
} else if (id != null) {
result = 1;
} else if (o.id != null) {
result = -1;
}
}
return result;
}
}
The most complex option but strict ordering is guaranteed: you can create counter service in your JVM and create events through factory, that will use that counter during event creation.
public class CounterService {
AtomicInteger counter = new AtomicInteger();
public int getNext() {
return counter.incrementAndGet();
}
}
public class SomeEntityFactory {
private CounterService counterService;
public SomeEntity create() {
return new SomeEntity(counterService.getNext());
}
}
public class SomeEntity {
private int order;
SomeEntity(int order) {
this.order = order;
}
}
Of course, this is example only, counter service might return BigInteger and be a web service, for instance. Or you can use a database sequence like a counter.
Related
Hibernate validator works well for me to validate objects fetched by hibernate, but the problem is that I would like to make sure that certain conditions are met after persisting/updating objects in database. For example:
My condition is: User can host at most 3 games
Constraint annotation:
#Target({ FIELD, TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = GamesCountValidator.class)
#Documented
public #interface ConstrainHostGamesCount {
String message() default "{com.afrixs.mygameserver.db.constraint.ConstrainHostGamesCount.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Validator:
public class GamesCountValidator implements ConstraintValidator<ConstrainHostGamesCount, User> {
#Override
public void initialize(ConstrainHostGamesCount constraintAnnotation) {
}
#Override
public boolean isValid(User user, ConstraintValidatorContext context) {
if (user == null)
return true;
return user.getGames().size() <= 3;
}
}
User class:
#Entity
#Table(name="Users")
#ConstrainHostGamesCount
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id", nullable=false, unique=true, length=11)
private int id;
#Column(name="name", length=30, unique=true)
private String name;
#OneToMany(mappedBy = "user", orphanRemoval = true, fetch = FetchType.LAZY)
private Set<Game> games = new HashSet<>();
//generic getters and setters
}
Game class:
#Entity
#Table(name="Games")
public class Game {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id", nullable=false, unique=true, length=11)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
#ConstrainHostGamesCount
private User user;
//generic getters and setters
}
Test method:
public class Test {
public static void hostGames(String userName, int count) {
try {
Session session = DatabaseManager.getSessionFactory().getCurrentSession();
session.beginTransaction();
Query userQuery = session.createQuery("from User where name = :name");
userQuery.setParameter("name", name);
User user = (User)userQuery.uniqueResult();
for (int i = 0; i < count; i++) {
Game = new Game();
game.setUser(user);
session.persist(game);
}
session.getTransaction().commit();
} catch (Exception e) {
DatabaseManager.getSessionFactory().getCurrentSession().getTransaction().rollback();
e.printStackTrace();
}
}
}
Desired behavior for Test.hostGames("afrixs", 4) would be to fail. However the validator validates the state of the user object before the update, ie. with games.size() equal to 0, so the constraint condition is met and nothing fails until Test.hostGames("afrixs", 4) is called for the second time. Of course in this situation we could manually add games to user user.getGames().add(game) but this attitude is error prone (the game needs to be added to user this way everywhere in the code) and it doesn't solve the problem if for example two Test.hostGames("afrixs", 2) are called asynchronously.
So my question is: what is the proper way of constraining the database integrity using hibernate? Is there a way to make the validator check the final state of objects after storing them into database? Or do I need to do the constraining manually (like performing another transaction after session.getTransaction().commit and check the conditions and roll back the updating transaction if they are not met)? Or should I leave out hibernate and use SQL triggers for this? Thank you for your answers, they will help a lot
And here is my current hibernate validation configuration:
<property name="javax.persistence.validation.group.pre-persist">javax.validation.groups.Default</property>
<property name="javax.persistence.validation.group.pre-update">javax.validation.groups.Default</property>
<property name="hbm2ddl.auto">validate</property>
Ok, I have made some experiments, writing down a small test class. To make things simple I changed the constraint to "User can host at most 1 game".
public class DBTest {
#Test
public void gamesCountConstraintWorking() {
DBManager.deleteHostedGames("afrixs");
boolean ok1 = DBManager.createOneGame("afrixs");
boolean ok2 = DBManager.createOneGame("afrixs");
int gamesCount = DBManager.deleteHostedGames("afrixs");
System.out.println("Sync test: count: "+gamesCount+", ok1: "+ok1+", ok2: "+ok2);
assertTrue(gamesCount <= 1);
assertTrue(!(ok1 && ok2));
}
#Test
public void gamesCountConstraintWorkingAsync() throws InterruptedException {
DBManager.deleteHostedGames("afrixs");
for (int i = 0; i < 30; i++) {
CreateOneGameRunnable r1 = new CreateOneGameRunnable(1);
CreateOneGameRunnable r2 = new CreateOneGameRunnable(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
int maxCount = 0;
while (r1.running || r2.running) {
int count = DBManager.selectHostedGamesCount("afrixs");
System.out.println("count: "+count);
maxCount = Math.max(maxCount, count);
}
t1.join();
t2.join();
int gamesCount = DBManager.deleteHostedGames("afrixs");
System.out.println("Async test: count: "+gamesCount+", maxCount: "+maxCount+", ok1: "+r1.ok+", ok2: "+r2.ok);
assertTrue(maxCount <= 1 && gamesCount <= 1);
assertTrue(!(r1.ok && r2.ok));
}
}
private class CreateOneGameRunnable implements Runnable {
public boolean ok;
public boolean running = true;
private int number;
CreateOneGameRunnable(int number) {
this.number = number;
}
#Override
public void run() {
System.out.println("Starting "+number);
ok = DBManager.createOneGame("afrixs");
System.out.println("Finished "+number);
running = false;
}
}
}
First I tried out #Guillaume's suggestion to use user.getGames().add(game); along with game.setUser(user); when assigning the relation. gamesCountConstraintWorking test was successful, however, gamesCountConstraintWorkingAsync wasn't. It means that this attitude was successful in maintaining the session consistency (at the cost of fetching all user games), however, the database integrity wasn't maintained.
A solution that actually worked for both tests was (as #OrangeDog suggested) to add the constraint directly into database schema. MySQL:
DELIMITER $$
CREATE TRIGGER check_user_games_count
AFTER INSERT
ON Games FOR EACH ROW
BEGIN
DECLARE gamesCount INT;
SET gamesCount = (SELECT COUNT(id) FROM Games WHERE user_id = new.user_id);
IF gamesCount > 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'User may host at most 1 game';
END IF;
END $$
DELIMITER ;
So my summary is that Hibernate works great as a layer above the database to work with, but if you want to make sure the persisted data look like you want, you need to dive directly into your database schema and perform actions in there. (But that's only the result of this experiment, maybe someone knows a solution for this using Hibernate)
Note: I tried the tests with BEFORE UPDATE triggers and random delays inside the triggers and the tests were successful as well, it seems like some kind of lock is acquired for the table while inserting, so yes, this is a safe solution. (Note2: BEFORE UPDATE trigger for this needs gamesCount+1 > 1 condition and the constraint could fail (not tested) in the case of inserting multiple rows in one query)
I have the following set of classes (along with a failing unit test):
Sprocket:
public class Sprocket {
private int serialNumber;
public Sprocket(int serialNumber) {
this.serialNumber = serialNumber;
}
#Override
public String toString() {
return "sprocket number " + serialNumber;
}
}
SlowSprocketFactory:
public class SlowSprocketFactory {
private final AtomicInteger maxSerialNumber = new AtomicInteger();
public Sprocket createSprocket() {
// clang, click, whistle, pop and other expensive onomatopoeic operations
int serialNumber = maxSerialNumber.incrementAndGet();
return new Sprocket(serialNumber);
}
public int getMaxSerialNumber() {
return maxSerialNumber.get();
}
}
SprocketCache:
public class SprocketCache {
private SlowSprocketFactory sprocketFactory;
private Sprocket sprocket;
public SprocketCache(SlowSprocketFactory sprocketFactory) {
this.sprocketFactory = sprocketFactory;
}
public Sprocket get(Object key) {
if (sprocket == null) {
sprocket = sprocketFactory.createSprocket();
}
return sprocket;
}
}
TestSprocketCache unit test:
public class TestSprocketCache {
private SlowSprocketFactory sprocketFactory = new SlowSprocketFactory();
#Test
public void testCacheReturnsASprocket() {
SprocketCache cache = new SprocketCache(sprocketFactory);
Sprocket sprocket = cache.get("key");
assertNotNull(sprocket);
}
#Test
public void testCacheReturnsSameObjectForSameKey() {
SprocketCache cache = new SprocketCache(sprocketFactory);
Sprocket sprocket1 = cache.get("key");
Sprocket sprocket2 = cache.get("key");
assertEquals("cache should return the same object for the same key", sprocket1, sprocket2);
assertEquals("factory's create method should be called once only", 1, sprocketFactory.getMaxSerialNumber());
}
}
The TestSprocketCache unit test always returns a green bar even if I change the following as follows:
Sprocket sprocket1 = cache.get("key");
Sprocket sprocket2 = cache.get("pizza");
Am guessing that I have to use a HashMap.contains(key) inside SprocketCache.get() method but can't seem to figure the logic.
The problem you're having here is that your get(Object) implementation only allows one instance to be created:
public Sprocket get(Object key) {
// Creates object if it doesn't exist yet
if (sprocket == null) {
sprocket = sprocketFactory.createSprocket();
}
return sprocket;
}
This is a typical lazy-loading instantiation singleton pattern. If you invoke get again, an instance will be assigned to sprocket and it will skip the instantiation completely. Note that you don't even use the key parameter at all, so it does not affect anything.
Using a Map would indeed be one way to achieve your objective:
public class SprocketCache {
private SlowSprocketFactory sprocketFactory;
private Map<Object, Sprocket> instances = new HashMap<Object, Sprocket>();
public SprocketCache(SlowSprocketFactory sprocketFactory) {
this.sprocketFactory = sprocketFactory;
}
public Sprocket get(Object key) {
if (!instances.containsKey(key)) {
instances.put(sprocket);
}
return instances.get(key);
}
}
Well, your current Cache implementation does not rely on key, so no wonder it always returns same cached-once value.
If you want to store different values for keys, and assuming you want it to be thread safe, you might end up doing something like this:
public class SprocketCache {
private SlowSprocketFactory sprocketFactory;
private ConcurrentHashMap<Object, Sprocket> cache = new ConcurrentHashMap<?>();
public SprocketCache(SlowSprocketFactory sprocketFactory) {
this.sprocketFactory = sprocketFactory;
}
public Sprocket get(Object key) {
if (!cache.contains(key)) {
// we only wan't acquire lock for cache seed operation rather than for every get
synchronized (key){
// kind of double check locking to make sure no other thread has populated cache while we were waiting for monitor to be released
if (!cache.contains(key)){
cache.putIfAbsent(key, sprocketFactory.createSprocket());
}
}
}
return cache.get(key);
}
}
Couple important side notes:
you'll need CocncurrentHashMap to ensure happens-before paradigm and so other thread will instantly see if cache has been filled;
new cache value creation has to be synchronized so each concurrent
thread won't generate it's own value, overriding previous values during race condition;
synchronization is quite expensive so we only wan't to engage it when needed, and due to same race condition you might get several threads holding monitor at the same time. That is why another check is required AFTER synchronized block to make sure that other thread hasn't already filled that value.
I'm trying to upgrade from Spring Data Neo4J 3 to 4 - I'm using Neo4J 2.2.2.
I use a GraphRepository instance to query the database, fetching back an object.
This object has several relationships, which are not fetched (deliberately, to avoid reading in the entire graph).
In the SDN3 code, simply used the Neo4JTemplate class to perform a fetch call for each relationship I needed to fetch. This worked extremely well.
However, in SDN4 this facility has been removed, and replaced by various implementations of the load() method. It's not clear from the documentation how to achieve what I did in SDN3.
To be clear: if I have a Set of objects in the first class I retrieve, governed by a relationship, I want to retrieve only the objects in that Set, not the entire collection of those objects in the database.
Have I missed something crucial in the upgrade process, or is there a simple way of doing what I'm trying to do?
Adding code:
My entity class:
#NodeEntity
public class File implements MetroNode {
private Long id;
private String fileName;
private SourceState sourceState;
private Set<State> states;
#GraphId
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
#Relationship(type = "HAS_FILE", direction = Relationship.INCOMING)
public SourceState getSourceState() {
return sourceState;
}
public void setSourceState(SourceState sourceState) {
this.sourceState = sourceState;
}
public State addState(MetroNode otherNode, StateStatus status) {
if (this.states == null) {
this.states = new HashSet<State>();
}
State state = new State(this, otherNode, status.toString());
this.states.add(state);
return state;
}
#Relationship(type = "HAS_STATE", direction = Relationship.OUTGOING)
public Set<State> getStates() {
return states;
}
public State getActiveState() {
if (this.states != null) {
for (State state : this.states) {
if (state.isActive()) {
return state;
}
}
}
return null;
}
}
My repository class:
public interface FileRepository extends GraphRepository<File> {
File findByFileName(String fileName);
}
When executing the getActiveState() method I get a null return, because the states Set is empty (hasn't been fetched).
Looking again at my code, I wonder if it's because I'm not using a "native" load method from the repository, but the overloaded version?
SDN 4 allows you to control loading of related entities with the persistence horizon.
Loading an entity with depth 0 will fetch properties of the entity and no related entities.
Depth 1 will fetch the first level of related entities, but not their relations and so on.
Controlling the depth by relationship type is not supported.
#CachePut or #Cacheable(value = "CustomerCache", key = "#id")
public Customer updateCustomer(Customer customer) {
sysout("i am inside updateCustomer");
....
return customer;
}
I found below documentation under CachePut source code
CachePut annotation does not cause the target method to be skipped -
rather it always causes the method to be invoked and its result to be
placed into the cache.
Does it mean if I use #Cacheable , updateCustomer method will be executed only once and result will be updated in cache. Subsequent calls to
updateCustomer will not execute updateCustomer , it will just update the cache.
While in case of #CachePut, updateCustomer method will be executed on each call and result will be updated in cache.
Is my understanding correct?
Yes.
I even made a test to be sure:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = CacheableTest.CacheConfigurations.class)
public class CacheableTest {
public static class Customer {
final private String id;
final private String name;
public Customer(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
}
final public static AtomicInteger cacheableCalled = new AtomicInteger(0);
final public static AtomicInteger cachePutCalled = new AtomicInteger(0);
public static class CustomerCachedService {
#Cacheable("CustomerCache")
public Customer cacheable(String v) {
cacheableCalled.incrementAndGet();
return new Customer(v, "Cacheable " + v);
}
#CachePut("CustomerCache")
public Customer cachePut(String b) {
cachePutCalled.incrementAndGet();
return new Customer(b, "Cache put " + b);
}
}
#Configuration
#EnableCaching()
public static class CacheConfigurations {
#Bean
public CustomerCachedService customerCachedService() {
return new CustomerCachedService();
}
#Bean
public CacheManager cacheManager() {
return new GuavaCacheManager("CustomerCache");
}
}
#Autowired
public CustomerCachedService cachedService;
#Test
public void testCacheable() {
for(int i = 0; i < 1000; i++) {
cachedService.cacheable("A");
}
Assert.assertEquals(cacheableCalled.get(), 1);
}
#Test
public void testCachePut() {
for(int i = 0; i < 1000; i++) {
cachedService.cachePut("B");
}
Assert.assertEquals(cachePutCalled.get(), 1000);
}
}
#CachePut always lets the method execute. It is generally used if you want your cache to be updated with the result of the method execution.
Example: When you want to update a stale data which is cached, instead of blowing the cache completely.
#Cacheable will be executed only once for the given cachekey and subsequent requests won't execute the method, until the cache expires or gets flushed.
Yes, you are absolutely correct.
#Cacheput and #Cacheable are used in conjunction.
#Cacheable will not update the cache on every call. In order to remove the stale data, there must be a service that uses the #Cacheput that clears the stale data.
Below answer is for the ones who are using guava caching to build cache.
Using guava caching, the time interval that is applied will empty the cache after a certain period of time which is not the case with #Cacheput. #Cacheput will only update the values that are stale and hence it calls the method every time to update the cache.
I hope my answer clears your question.
I have an entity which has some BIT fields into the database:
editable
needs_review
active
These fields are mapped against boolean fields in its Java class using Hibernate 3.6.9 version. That forces me to write an interface method for each List of entities I want to get:
List<Entity> listEditables();
List<Entity> listReviewNeeded();
List<Entity> listActives();
Or write a general interface method to achieve a combination of them:
List<Entity> listEntities(boolean editables, boolean reviewNeeded, boolean actives);
That second choice looks greater, but if I add another field in the future there will be a need to modify the interface itself (and every line of code coupled to it).
So I decided I can express it as an enumeration Set:
public enum EntityType{
EDITABLE, REVIEW_NEEDED, ACTIVE
}
//That way there's no need to change interface method's signature
List<Entity> listEntities(Set<EntityType> requiredTypes);
It makes sense that being an enumeration match what I want to achieve, the Entity type itself should have its own Set<EntityType>:
public class Entity{
Set<EntityType> entityTypes;
}
However instead of that I have the mapped booleans which logically match that Set. Then my question, is there any way to map Set<EntityType> entityTypes in hibernate based in that BIT fields or do I have to manage that logic myself having them as boolean?
UPDATE
Having them mapped as a Set implies the possibility of querying for a List using an in clause, if not it would imply an extra step for conversion between my controller and model codes.
Set<EntityType> typesSet = Sets.newHashSet(EntityType.EDITABLE, EntityType.REVIEW_NEEDED);
//Obtains a list of every single entity which is EDITABLE or REVIEW_NEEDED
session.createCriteria(Entity.class).addRestriction(Restrictions.in("entityTypes",typeSet)).list();
I think I have a solution for you. What you are interested in is a CompositeUserType.
As an example lets use a InetAddress composite user type I wrote lately to map a 128bit IPv6 Address / IPv4Address object to two 64bit long properties inside a user account entity.
The signupIp:InetAddress is mapped towards two columns (there is no column count limit or alike) using:
#Columns(columns = {#Column(name = "ip_low", nullable = true), #Column(name = "ip_high", nullable = true)})
private InetAddress signupIp;
And the interesting part of the implementation looks like this:
public class InetAddressUserType implements CompositeUserType {
#Override
public String[] getPropertyNames() {
return new String [] {"ipLow", "ipHigh"};
}
#Override
public Type[] getPropertyTypes() {
return new Type [] { LongType.INSTANCE, LongType.INSTANCE};
}
#Override
public Object getPropertyValue(Object component, int property) throws HibernateException {
if(component != null)
return toLong((InetAddress)component)[property];
else
return null;
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException {
if(value != null) {
long [] longs = toLong((InetAddress)value);
st.setLong(index, longs[0]);
st.setLong(index + 1, longs[1]);
}
else {
st.setNull(index, LongType.INSTANCE.sqlType());
st.setNull(index + 1, LongType.INSTANCE.sqlType());
}
}
#Override
public void setPropertyValue(Object component, int property, Object value)
throws HibernateException {
throw new RuntimeException("This object is immutable");
}
#Override
public Class<?> returnedClass() {
return InetAddress.class;
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
return x != null ? x.equals(y) : null == y;
}
#Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
Long ipLow = rs.getLong(names[0]);
if(!rs.wasNull()) {
Long ipHigh = rs.getLong(names[1]);
try {
return fromLong(new long [] {ipLow, ipHigh});
} catch (UnknownHostException e) {
throw new HibernateException("Failed to get InetAddress: ip = " + ipHigh + " + " + ipLow, e);
}
}
else
return null;
}
#Override
public Object deepCopy(Object value) throws HibernateException {
if(value != null)
try {
return InetAddress.getByAddress(((InetAddress)value).getAddress());
} catch (UnknownHostException e) {
throw new RuntimeException("Impossible Exception: " + e.getMessage(), e);
}
else
return null;
}
#Override
public boolean isMutable() {
return false;
}
...
}
Note that I flexibly switch between Inet4Address and Inet6Address instances depending on the values of ipLow and ipHigh. The composite is marked as immutable and you need to check the documentation and the examples in the Hibernate source code (build in composite user types).
In a similar way you can map your meaningful bit properties. You can query those bits by using a single Restriction.eq refering to your EnumType. You can use the equals method to check the properties object. And if you need to refer to a special mapped bit you can use the dot notation like in signupIp.ipLow to refer to the ipLow property/column.
I guess this is what you are looking for.
Update:
In the end it boils down to define the right order of your properties. Hibernate will always use integer index values to access each property:
//immutable for simplicity
class Status {
private final boolean editable;
private final boolean needsReview;
private final boolean active;
//... constructor + isEditable etc..
}
In your StatusCompositeType class:
public String[] getPropertyNames() {
return new String [] {"editable", "needsReview", "active"};
}
public Type[] getPropertyTypes() {
return new Type [] { BooleanType.INSTANCE, LongType.INSTANCE};
}
public Object getPropertyValue(Object component, int property) throws HibernateException {
if(component != null) {
Status status = (Status)component;
switch(property) {
case 1: return status.isEditable();
case 2: return status.isReviewNeeded();
case 3: return status.isActive();
default: throw new IllegalArgumentException();
}
}
else
return null; //all columns can be set to null if you allow a entity to have a null status.
}
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException {
if(value != null) {
Status status = (Status)value;
st.setBoolean(index, status.isEditable());
st.setBoolean(index + 1, status.isReviewNeeded());
st.setBoolean(index + 2, status.isActive());
}
else {
st.setNull(index, BooleanType.INSTANCE.sqlType());
st.setNull(index + 1, BooleanType.INSTANCE.sqlType());
st.setNull(index + 2, BooleanType.INSTANCE.sqlType());
}
}
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
Boolean isEditable = rs.getBoolean(names[0]);
if(!rs.wasNull()) {
Boolean isReviewNeeded = rs.getBoolean(names[1]);
Boolean isActive = rs.getBoolean(names[2]);
return new Status(isEditable, isReviewNeeded, isActive);
}
else
return null;
}
The rest is straight forward. Remember to implement equals and hashcode for the user type and add the type to the configuration before you create your sessionFactory.
Once you have everything in place you can create a criteria search and use:
//search for any elements that have a status of editable, no reviewNeeded and is not active (true false false).
criteria.add(Restrictions.eq("status", new Status(true, false, false));
Now your listEntities method may become either: listEntities(Status status) or listEntities(boolean editable, boolean reviewNeeded, boolean isActive).
If you need additional information just check the CompositeType and BasicType implementations Hibernate provides within its own sourcecode (look for implementors of CompositeType and BasicType). Understanding those helps alot to use and learn this intermediate level knowledge of Hibernate.
After some brainstorming, I've gone to a workaround which I consider the second best one being imposible to map an enum for the booleans in Hibernate. This is how I have my Entity class looks now:
public class Entity{
private boolean editable;
private boolean needsReview;
private boolean active;
//getters and setters
}
My listing method is implemented as this:
public List<Entity> listEntities(Set<EntityType> requiredTypes){
Criteria cri = session.createCriteria(Entity.class);
if (requiredTypes.contains(EntityType.EDITABLE)){
cri.addRestriction(Restrictions.eq("editable",true));
}
if (requiredTypes.contains(EntityType.NEEDS_REVIEW)){
cri.addRestriction(Restrictions.eq("needsReview",true));
}
if (requiredTypes.contains(EntityType.ACTIVE)){
cri.addRestriction(Restrictions.eq("active",true));
}
return cri.list();
}
Not bad, but don't know if it's the only way to go with that!
I don't think hibernate provides a way to manage the mappings the way you're describing. You can create your own UserType (https://community.jboss.org/wiki/Java5EnumUserType) but every time you add a new enum value you will have to change the logic in the UserType to map the new field as well.
The alternative will be to convert this into a one to many relationship. Your point is basically that if you want to add more fields you will have to change the signature of listEntities but also you will have to modify your table.
So, instead you can create a table that will contain your entity types and have a #OneToMany` relationship to it from your entity. For example:
Define your flags as required:
public enum Flags {
EDITABLE, REVIEW_NEEDED, ACTIVE
}
Create a one-to-many relationship to EntityType:
#Entity
#Table( name="entity" )
public class Entity implements Serializable {
#OneToMany(mappedBy = "entity")
public Set<EntityType> getEntityTypes() {
return entityTypes;
}
And a many-to-one to Entity:
#Entity
#Table( name="entityType" )
public class EntityType implements Serializable {
#Id
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ENTITY_ID")
private Entity entity;
#Enumerated(EnumType.STRING)
private Flag entityType;
...
}
PD: Please note the code is just an example and is not complete or tested.