EDIT: Sample project available on github.
I'm using Neo4J (Rest graph database, hosted in grapheneDb) and Spring Data in our backend project.
<bean id="graphDatabaseService" class="org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase">
I have a simple one-to-many relationship between two entities: User and Stay.
EDIT: I thought this wasn't relevant for the issue, but after seeing a similar problem in SDN4, I think I need to update the question (there is a basic #NodeEntity class, and both entities are extending this base class).
#NodeEntity
public abstract class BasicNodeEntity implements Serializable {
#GraphId
private Long nodeId;
}
public class User extends BasicNodeEntity {
#RelatedTo(type = "HAS_STAY", direction = Direction.OUTGOING)
Set<Stay> stays;
public void addStay(Stay stay) {
stays.add(stay);
}
}
public class Stay extends BasicNodeEntity {
#RelatedTo(type = "HAS_STAY", direction = Direction.INCOMING)
User user;
}
I'm unable to persist more than one stay. The first stay I add to the user is persisted correctly, but just the first one. The next stays added never persists, and I always retrieve the first one.
The method I use to create a new stay is:
#Autowired
Neo4jOperations template;
#Transactional
private void createStay(Stay stay, User user) throws Exception {
stay = template.save(stay);
user.addStay(stay);
template.save(user);
// If i evaluate user at this point, it contains both stays
// But if I retrieve the user from the repository, it just contains
// the first stay, the second one has not persisted.
}
EDIT: User modified is retrieved correctly through UserRepository.
public interface UserRepositoryCustom {}
public interface UserRepository extends GraphRepository<User>, UserRepositoryCustom {
User findById(String id);
}
User user = userRepository.findById(userId);
NOTE: I also tried to save through the repository interface instead of the Neo4jTemplate one, but I have the same problem.
Both entities are correctly saved in the neo4j database, it's just a persistence issue.
I think this should be quite easy, so I'm probably missing something..
Any help would be greatly appreciated.
Relevant versions:
<spring.version>4.0.5.RELEASE</spring.version>
<spring-data-neo4j.version>3.3.2.RELEASE</spring-data-neo4j.version>
There is another SO question with a very similar problem, but without response so far.
It is a tricky thing.
Your custom equals method causes two entities which have their node-id set but not yet their uuid-id set, to be equal so that when loading them into a set the set will only contain one.
Code in: RelationshipHelper
protected Set<Object> createEntitySetFromRelationshipEndNodes(Object entity, final MappingPolicy mappingPolicy, final Class<?> relatedType) {
final Iterable<Node> nodes = getStatesFromEntity(entity);
final Set<Object> result = new HashSet<Object>();
for (final Node otherNode : nodes) {
Object target = template.createEntityFromState(otherNode, relatedType, mappingPolicy);
result.add(target);
}
return result;
}
If you change your code to have an equals/hashcode in your BasicNode entity:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BasicNodeEntity)) return false;
BasicNodeEntity that = (BasicNodeEntity) o;
if (nodeId != null) {
if (!nodeId.equals(that.nodeId)) return false;
} else {
if (that.nodeId != null) return false;
}
return true;
}
#Override
public int hashCode() {
return nodeId != null ? nodeId.hashCode() : 0;
}
so that entities that have only a nodeId set are comparable
and adapt the subclass methods
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof IdentifiableEntity)) return false;
IdentifiableEntity entity = (IdentifiableEntity) o;
//change
if (!super.equals(o)) return false;
if (id != null) {
if (!id.equals(entity.id)) return false;
} else {
if (entity.id != null) return false;
}
return true;
}
#Override
public int hashCode() {
//change
if (super.hashCode() != 0) return super.hashCode();
return id != null ? id.hashCode() : 0;
}
Then it works.
Going forward if you are working with Neo4j Server I recommend to you to check out SDN 4 RC2 instead which was released on Friday.
Related
I have an application that takes json objects from a queue, deserializes them to a model, applies a list of filters, and sends the objects that pass all filters through to another queue.
The two complicating criteria are:
The set of filters is determined and injected via Spring profile at startup.
The type of object that the json is being deserialized to is also determined the by the Spring profile at startup.
The following solution is ugly because it involves casting:
public class MessageTypeOne {
public int someField;
}
public class MessageTypeTwo {
public int otherField;
}
public interface MessageFilter {
boolean doesFilterPass(Object object);
}
#Component
#Profile("ProfileOne")
public class OneOfMyMessageFilters implements MessageFilter {
public boolean doesFilterPass(Object object) {
MessageTypeOne message = (MessageTypeOne)object;
if (message.someField == something) {
return false;
} else return true;
}
}
#Component
#Profile("ProfileTwo")
public class AnotherOneOfMyMessageFilters implements MessageFilter {
public boolean doesFilterPass(Object object) {
MessageTypeTwo message = (MessageTypeTwo)object;
if (message.otherField == something) {
return false;
} else return true;
}
}
#Service
public class MessageFilterService {
// injected at runtime via Spring profile
private Set<MessageFilter> messageFilters
#AutoWired
public MessageFilterService(Set<MessageFilter> messageFilters) {
this.messageFilters = messageFilters;
}
public boolean passesAllFilters(Object object) throws IOException {
for (MessageFilter filter : messageFilters) {
if (!filter.doesFilterPass(object)) {
return false;
}
}
return true;
}
}
What's the cleanest pattern for cases like these? I've read about the visitor pattern but I'm not sure that's any better than casting like this.
As far as design pattern is concerned, I think it is of type Strategy pattern. I am not talking about Spring way of implementation. You may have n number of filters, but you have to choose based upon the context. So strategy pattern is best fitted here. Others can provide other patterns. You can strategy pattern in the below link.
https://en.wikipedia.org/wiki/Strategy_pattern
What about visitor pattern with Java reflection? Here is an old article:
https://www.javaworld.com/article/2077602/java-tip-98--reflect-on-the-visitor-design-pattern.html
When you want to decouple messages from filters and relation is many to many you can always use Chain of Responsibility.
#Service
public class MessageFiltersAggregator {
private MessageFilter chainEntryNode;
#AutoWired
public MessageFilterService(Set<MessageFilter> messageFilters) {
this.chainEntryNode = buildChain(messageFilters);
}
public boolean passesAllFilters(Object object) throws IOException {
return chainEntryNode.doesFilterPass(object);
}
}
You need to implement buildChain method which creates chain from collection. Of course, each element in chain should have next property. In this case MessageFilter could look like below:
public abstract class MessageFilter {
private MessageFilter next;
//constructors, setters, etc
public boolean doesFilterPass(Object object) {
boolean res = true;
if (canHandle(object)) {
res = validate(object);
}
return res && next.doesFilterPass(object);
}
public abstract boolean validate(Object object);
public abstract boolean canHandle(Object object);
}
Abstract class contains chain logic you just need to implement two methods in each subclass. One of implementation could look like below:
public class AnotherOneOfMyMessageFilters extends MessageFilter {
public boolean canHandle(Object object) {
return object instanceof MessageTypeTwo;
}
public boolean validate(Object object) {
MessageTypeTwo message = (MessageTypeTwo)object;
return message.otherField == something;
}
}
All above classes are just example created without IDE so could have issues in syntax but should give you an idea how it should work.
See also:
Chain of Responsibility in Java
Chain of Responsibility Design Pattern in Java
If I understand your problem correctly, then it's possible to configure your Spring profile in a way that makes your filters throw ClassCastExceptions.
Assuming that you configuration options are the way you want, then it demonstrates the only real problem with your design -- your filters can be applied to any Object, and that's what the interface says -- doesFilterPass( Object ) -- but your filters only really work with certain types of objects.
That's what you need to fix. If the filter is applied to a strange type of object, does it pass or fail? You can decide this on a per-filter basis and then just fix it like this:
public boolean doesFilterPass(Object object) {
if (!(object instanceOf MessageTypeTwo)) {
return true;
}
MessageTypeTwo message = (MessageTypeTwo)object;
if (message.otherField == something) {
return false;
} else return true;
}
Easy peasy.
I know you don't like the cast, but it's a direct result of the configuration options you provide -- the profile can be configured to apply filters to any kind of object. You just need to support that, and that means there has to be casting somewhere.
This became much cleaner with generics. Since I know what type of Object each filter can handle I can just do this, eliminating the casting:
public class MessageTypeOne {
public int someField;
}
public class MessageTypeTwo {
public int otherField;
}
public interface MessageFilter<T> {
boolean doesFilterPass(T message);
}
#Component
#Profile("ProfileOne")
public class OneOfMyMessageFilters<T extends MessageTypeOne> implements MessageFilter<T> {
public boolean doesFilterPass(MessageTypeOne message) {
if (message.someField == something) {
return false;
} else return true;
}
}
#Component
#Profile("ProfileTwo")
public class AnotherOneOfMyMessageFilters<T extends MessageTypeTwo> implements MessageFilter<T> {
public boolean doesFilterPass(MessageTypeTwo message) {
if (message.otherField == something) {
return false;
} else return true;
}
}
#Service
public class MessageFilterServiceImpl<T> implements MessageFilterService<T> {
// injected at runtime via Spring profile
private Set<MessageFilter<T>> messageFilters
#AutoWired
public MessageFilterService(Set<MessageFilter<T>> messageFilters) {
this.messageFilters = messageFilters;
}
public boolean passesAllFilters(T message) throws IOException {
for (MessageFilter filter : messageFilters) {
if (!filter.doesFilterPass(message)) {
return false;
}
}
return true;
}
}
public interface MessageFilterService<T> {
boolean passesAllFilters(T rawEvent) throws IllegalArgumentException;
}
I have Set of Object which has existing values and getting a new Set values as an update. If the new Set does contain the old Object then I do nothing, if the new Set contains new Object then I want send create update and if the new Set doesn't contain an existing object then I want send a delete update.
Object has two fields :
private PreferenceType preferenceType;
private String preferenceValue;
Currently I am comparing the existing Set objects against new Set objects and if I don't find any existing object in new set then sending the delete update.
private void sendDeletePreferenceMessage(Set<AccountPreference> existingAccountPreferences, Set<AccountPreference> accountPreferencesFromRequest) {
int counter = 0;
for(AccountPreference accountPreference : existingAccountPreferences) {
for(AccountPreference accountPreference1: accountPreferencesFromRequest) {
if(accountPreference.getPreferenceType().equals(accountPreference1.getPreferenceType()) &&
accountPreference.getPreferenceValue().equals(accountPreference1.getPreferenceValue()))
counter++;
}
if(counter == 0) {
accPrefDeleteSender.send(accountPreference);
}
}
}
And also comparing the new set of objects against existing set of Objects to find the new updates that I want send as a create update
private void sendCreatePreferenceMessage(Set<AccountPreference> accountPreferencesFromRequest, Set<AccountPreference> existingAccountPreferences) {
int counter = 0;
for(AccountPreference accountPreference : accountPreferencesFromRequest) {
for(AccountPreference accountPreference1: existingAccountPreferences) {
if(accountPreference.getPreferenceType().equals(accountPreference1.getPreferenceType()) &&
accountPreference.getPreferenceValue().equals(accountPreference1.getPreferenceValue()))
counter++;
}
if(counter == 0) {
accPrefCreateSender.send(accountPreference);
}
}
}
This works perfectly but I believe this could be simplified in a better way. Any suggestion of doing this better!
You are using two AccountPreference Objects: one with id field, and another without id. I don't know if it's possible without using interfaces or abstract classes. Nevertheless, according to your comments it seems to me you're confused how to override Object.equals method, so I'll give you an example:
class MyObjectWithId {
private long id;
private String someField;
public String getSomeField() {
return someField;
}
#Override
public boolean equals(Object other){
if (this == other) return true;
if (other == null || !other.getClass().isInstance(MyObject.class)) return false;
MyObject myObject = (MyObject) other;
return this.getSomeField().equals(myObject.getSomeField());
}
}
class MyObject {
private String someField;
public String getSomeField() {
return someField;
}
#Override
public boolean equals(Object other){
if (this == other) return true;
if (other == null || !other.getClass().isInstance(MyObjectWithId.class)) return false;
MyObjectWithId myObjectWithId = (MyObjectWithId) other;
return this.getSomeField().equals(myObjectWithId.getSomeField());
}
}
As you can see, you can ask if the two different types of Object are equal (in this case, there isn't need for overriding hashCode).
Now you should be able to do the rest (regarding your original question about two Sets)
Set has a method boolean contains(Object o).
You don't need two for loops, just iterate through one Set and check if another Set contains() the object!
You also need to override the boolean equals(Object o) in the POJO!
class MyObject {
private PreferenceType preferenceType;
private String preferenceValue;
#Override
public boolean equals(Object o){
if (this == o)
return true;
if (o == null || o.getClass()o.getClass() != getClass())
return false;
if(this.getPreferenceType().equals(o.getPreferenceType()) && this.getPreferenceValue().equals(o.getPreferenceValue()))
return true;
return false;
}
}
}
me and my team are working on an upgrade of our company's system which as getting kind of forgotten and was running old versions of everything it uses; so developing newer features was becoming a pain with newer and unsupported technologies.
So far we have managed to produce an almost fully working version of the system; but we got stuck at a feature which involves Datanucleus-JDO, MongoDB and inheritance.
We have some models which are tremendously similar (from the code's perspective). In the current in-production version, to apply a change to it usually involves to rewrite the same piece of code in all classes, so we thought that inheritance would make the job easier and better. So we have two interfaces at the top hierarchy level (which as far we know, Datanuclues nor MongoDB doesn't care about them at all); which go like this:
public interface Entity extends Serializable {
String getDate();
double getQty();
void setQty(double qty);
void setDate(String date);
void setKey(Key key);
}
And
public interface HourEntity extends Entity {
String getHour();
}
We use application defined keys, we use this unique class to build different kind of keys. We only want the toString representation of this class to store and retrieve data in Mongo.
public final class Key implements Serializable {
static final long serialVersionUID = -448150158203091507L;
public final String targetClassName;
public final String id;
public final String toString;
public final int hashCode;
public Key() {
targetClassName = null;
id = null;
toString = null;
hashCode = -1;
}
public Key(String str) {
String[] parts = str.split("\\(");
parts[1] = parts[1].replaceAll("\\)", " ");
parts[1] = parts[1].replace("\"", " ");
parts[1] = parts[1].trim();
this.targetClassName = parts[0];
this.id = parts[1];
toString = this.toString();
hashCode = this.hashCode();
}
public Key(String classCollectionName, String id) {
if (StringUtils.isEmpty(classCollectionName)) {
throw new IllegalArgumentException("No collection/class name specified.");
}
if (id == null) {
throw new IllegalArgumentException("ID cannot be null");
}
targetClassName = classCollectionName;
this.id = id;
toString = this.toString();
hashCode = this.hashCode();
}
public String getTargetClassName() {
return targetClassName;
}
public int hashCode() {
if(hashCode != -1) return hashCode;
int prime = 31;
int result = 1;
result = prime * result + (id != null ? id.hashCode() : 0);
result = prime * result + (targetClassName != null ? targetClassName.hashCode() : 0);
return result;
}
public boolean equals(Object object) {
if (object instanceof Key) {
Key key = (Key) object;
if (this == key)
return true;
return targetClassName.equals(key.targetClassName) && Objects.equals(id, key.id);
} else {
return false;
}
}
public String toString() {
if(toString != null) return toString;
StringBuilder buffer = new StringBuilder();
buffer.append(targetClassName);
buffer.append("(");
if (id != null) {
buffer.append((new StringBuilder()).append("\"").append(id)
.append("\"").toString());
} else {
buffer.append("no-id-yet");
}
buffer.append(")");
return buffer.toString();
}
}
This application defined identity is working fine on all other models which does not involve inheritance.
This is one of the actual models that we intend to store in our datastore:
#PersistenceCapable(detachable="true")
#Inheritance(strategy=InheritanceStrategy.COMPLETE_TABLE)
public class Ticket implements Entity {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.UNSPECIFIED, column="_id")
protected Key key;
protected String date;
protected int qty;
public Ticket() {
this.qty = 0;
}
public Key getKey() {
return key;
}
#Override
public void setKey(Key key) {
this.key = key;
}
public double getQty() {
return qty;
}
public void setQty(double qty) {
this.qty = (int) qty;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Ticket other = (Ticket) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
return true;
}
#Override
public String toString() {
return "Ticket [key=" + key + ", date=" + date + ", qty="
+ qty + "]";
}
}
And this is its subclass (all models which involve this problem just involve one super class and only one children per every super class):
#PersistenceCapable(detachable="true")
#Inheritance(strategy=InheritanceStrategy.COMPLETE_TABLE)
public class HourTicket extends Ticket implements HourEntity {
private String hour;
public HourTicket() {
super();
}
public Key getKey() {
return key;
}
#Override
public void setKey(Key key) {
this.key = key;
}
public String getHour() {
return hour;
}
public void setHour(String hour) {
this.hour = hour;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
HourTicket other = (HourTicket) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
return true;
}
#Override
public String toString() {
return "HourTicket [key=" + key + ", date=" + date
+ ", hour=" + hour + ", qty=" + qty + "]";
}
}
Finally, the persisntance.xml is like this
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
<!-- JOSAdmin "unit" -->
<persistence-unit name="ourdatastore">
<class>mx.ourdomain.Ticket</class>
<class>mx.ourdomain.HourTicket</class>
<exclude-unlisted-classes/>
</persistence-unit>
</persistence>
And package-mongo.orm
<?xml version="1.0"?>
<!DOCTYPE orm SYSTEM "file:/javax/jdo/orm.dtd">
<orm>
<package name="mx.ourdomain" >
<class name="Ticket" table="Ticket">
<field name="key" primary-key="true" >
<column name="_id" length="100" />
</field >
</class>
<class name="HourTicket" table="HourTicket">
<primary-key >
<column name="_id" target="_id" />
</primary-key>
</class>
</package>
</orm>
So, the problems comes when trying to perform any read or write operations using either the super class or the subclass. This has happened with the same exact results in several (all possible as far we know) scenarios, but the test scenario we are study begins with this call:
Ticket ticket = persistenceManager.getObjectById(Ticket.class, key);
The key is generated with an standard procedure which is used by other models which do store and read successfully; and of course, it is of the previously shown key class.
We have gone as far as debugging the datanucleus tasks beyond this. And we have found that as expected:
The metadata shows that its the super class of others.
Its using application managed keys.
But when trying to get the class name to determine which is the correct Mongo collection to query, datanucleus-mongodb tries to query both classes (Ticket and HourTicket); but then it handles to the mongo driver the key object perse, and then a CodecConfigurationException is thrown since mongo does not know how to work with the key class (when building the query, datanucleus-mongo creates a BasicDBObject which has the structure {_id:key}, which cannot be constructed without the codec because of the key entry. This happens at the MongoDBUtils class in the datanucleus-mongodb project v5.1.0; class MongoDBUtils, method getClassNameForIdentity(Object, AbstractClassMetaData, ExecutionContext, ClassLoaderResolver)).
So, we suppose that we have some configuration missing to tell datanucleus that it should use the toString() form of the key; since the Monogo driver handles String just fine (datanuclues docs actually states that when using custom classes as datastore keys it will use the toString() form of the key; so I'm unsure if this could be a bug).
We have tried to use a KeyTraslator plugin and making the key class a DatastoreId and wrapping in a StringId with no success: the same exception is fired, except when wrapping the Key class in a StringId: the mongo lecture is sucessful but then when trying to build the model object, an ClassCastException is thrown since String cannot be casted into Key, and refactoring the code to use a String key will badly break data already in database; since it has a special format the key class can read and produce.
Is there something we are missing using inheritance with datanucleus JDO w/mongoDB?
I was not putting much attention to the settings around the objectIdClass metadata; since from the docs I got that they were intended for composed keys only. It results that if you define an objectId class with only one attribute; then it behaves as a custom SingleFieldId; which is what we wanted.
I found "funny" the fact that non annotated (or non declared metadata for objectIdClass) classes will work fine and the custom key used will be threated just fine; but once you make any of them a super class, then you are obligated to add the objectIdClass metadata.
Beside annotating the Ticket class (and all other super classes) with objectIdClass, we:
Removed the toString and hashCode attributes from the Key class (#NotPersistent and transient keyword won't make Datanucleus ignore them; so I guess there is no performance improvement for toString() and hashCode() methods on custom keys right now).
Removed all the final qualifiers from the Key class attributes (Datanucleus docs don't say that custom key fields cannot be final; but guess what, they can't be)
Changed the Key key class member from all superclass for String id (as in the key class). We also had to change the implementation of the getters and setters for the id member; using the required string constructor of the key class to build the key when calling the method. Of course, the "key" field declared in the package-mongo.orm was changed to id in the super classes.
And that was it! with those little changes our system is working great; no other changed were required on other persistable classes nor DAOs.
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.
See code just bellow
Our generic interface
public interface Repository<INSTANCE_CLASS, INSTANCE_ID_CLASS> {
void add(INSTANCE_CLASS instance);
INSTANCE_CLASS getById(INSTANCE_ID_CLASS id);
}
And a single class
public class Order {
private Integer id;
private Integer orderNumber;
// getter's and setter's
public void equals(Object o) {
if(o == null)
return false;
if(!(o instanceof Order))
return false;
// business key
if(getOrderNumber() == null)
return false;
final Order other = (Order) o;
if(!(getOrderNumber().equals(other.getOrderNumber())))
return false;
return true;
}
// hashcode
}
And when i do the following test
private Repository<Order, Integer> repository;
#Before
public void setUp {
repository = EasyMock.createMock(Repository.class);
Order order = new Order();
order.setOrderNumber(new Integer(1));
repository.add(order);
EasyMock.expectLasCall().once();
EasyMock.replay(repository);
}
#Test
public void addOrder() {
Order order = new Order();
order.setOrderNumber(new Integer(1));
repository.add(order);
EasyMock.verify(repository)
}
I get
Unexpected method call add(br.com.smac.model.domain.Order#ac66b62):
add(br.com.smac.model.domain.Order#ac66b62): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:43)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:72)
at $Proxy4.add(Unknown Source)
Why does it not work as expected ??? What should i do to pass the test ??? Any workaround ???
I copied your code and ran it with Java 1.6 using EasyMock 2.0 and it works just fine. I had to fix some parts like public void equals(Object o) which should be public boolean equals, but it seems clear that you had it right in your real code.
Are you using another EasyMock version or do I miss something?
Cheers
Thomas