HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Generic toString() for JPA base entity class

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
genericjpaforclasstostringentitybase

Problem

In a Java/JPA project I usually define a base class (on which I put the id). The goal is to implement an generic toString() on this base class (named BaseEntity) from which all other entities inherit. We played around with apache commons ReflectionToStringBuilder, but we ran into problems because of cyclic references and the lack of different handling depending on the type of the fields.

My wish is that, if an Entity is printed/logged I get all field names and their values except for fields of type collection, in this case we just want the IDs of these objects to avoid recursion (PS: We can assume that collections on entities always contain other entities and they have an ID)

The desired output pattern is the following

classname[fieldname1=value1, fieldname2=value2,..., collectionname1=[id1,id2,..], collectionname2=[id1,id2,...],...]


a sample output for an entity "Project" which subclasses BaseEntity may look like this:

Project[nameLong=Testproject, nameShort=tpr, support=false, active=true, priority=0, blockers=[20], responsibilities=[13,14,15]]


We came up with the following implementation using reflection:

```
@Override
public String toString() {
// init container for field names
List fieldToPrintout = new ArrayList(0);

// get all fields from this object (use declared fields to get private
// and protected fields)
Field[] fieldsOnTheObject = this.getClass().getDeclaredFields();

// set the accessibility for field which are private
AccessibleObject.setAccessible(fieldsOnTheObject, true);

for (Field field : fieldsOnTheObject) {
try {
// ignore static fields
if (!Modifier.isStatic(field.getModifiers())) {
// get the object from the field
Object fieldValue = field.get(this);
// check if the field is a collection
if (Collection.class.isAssignableFrom(field.getType())) {
// check if the collectio

Solution

So, there are a couple of things that can be done with this. First, use methods! Short, well-named methods can make it much easier to read the code. Second, use short-circuiting logic. continue is your friend. Most of your comments don't really contribute much, or can be obviated with good method names. You can also set the size of the ArrayList at creation time, because you know how many fields you're going to be dealing with. In my opinion, something like this looks a lot cleaner:

@Override
public String toString() {

    final Field[] fields = this.getClass().getFields();
    final List fieldsToPrintOut = new ArrayList(fields.length);

    AccessibleObject.setAccessible(fields, true);

    for (final Field field : fields) {
        if (Modifier.isStatic(field.getModifiers())) {
            continue;
        }

        final Object fieldValue;
        try {
            fieldValue = field.get(this);
        } catch (final IllegalAccessException ex) {
            logger.catching(ex);
            continue;
        }

        if (!isCollection(field)) {
            fieldsToPrintOut.add(field.getName() + "=" + fieldValue);
            continue;
        }

        if (!isParameterized(field) || !isBaseEntity(field)) {
            continue;
        }

        final List ids =
                ((Collection) fieldValue).stream().map(p -> p.getId().toString()).collect(Collectors.toList());
        fieldsToPrintOut.add(field.getName() + ids);

    }
    return MessageFormat.format(this.getClass().getSimpleName() + fieldsToPrintOut);
}

private static boolean isCollection(final Field field) {
    return Collection.class.isAssignableFrom(field.getType());
}

private static boolean isParameterized(final Field field) {
    return field.getGenericType() instanceof ParameterizedType;
}

private static boolean isBaseEntity(final Field field) {
    final Class genericType =
            (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];

    return BaseEntity.class.isAssignableFrom(genericType);
}

Code Snippets

@Override
public String toString() {

    final Field[] fields = this.getClass().getFields();
    final List<String> fieldsToPrintOut = new ArrayList<String>(fields.length);

    AccessibleObject.setAccessible(fields, true);

    for (final Field field : fields) {
        if (Modifier.isStatic(field.getModifiers())) {
            continue;
        }

        final Object fieldValue;
        try {
            fieldValue = field.get(this);
        } catch (final IllegalAccessException ex) {
            logger.catching(ex);
            continue;
        }

        if (!isCollection(field)) {
            fieldsToPrintOut.add(field.getName() + "=" + fieldValue);
            continue;
        }

        if (!isParameterized(field) || !isBaseEntity(field)) {
            continue;
        }

        final List<String> ids =
                ((Collection<BaseEntity>) fieldValue).stream().map(p -> p.getId().toString()).collect(Collectors.toList());
        fieldsToPrintOut.add(field.getName() + ids);

    }
    return MessageFormat.format(this.getClass().getSimpleName() + fieldsToPrintOut);
}

private static boolean isCollection(final Field field) {
    return Collection.class.isAssignableFrom(field.getType());
}

private static boolean isParameterized(final Field field) {
    return field.getGenericType() instanceof ParameterizedType;
}

private static boolean isBaseEntity(final Field field) {
    final Class<?> genericType =
            (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];

    return BaseEntity.class.isAssignableFrom(genericType);
}

Context

StackExchange Code Review Q#108392, answer score: 5

Revisions (0)

No revisions yet.