12

All my JPA entity classes implement an interface called Entity which is defined like this:

public interface Entity extends Serializable {
// some methods

}

Some of the fields of my JPA entity have @Column annotation on top of them and some don't. MyEntity class is defined like below:

@Entity
public class MyEntity implements Entity {

   @Id
   private Long id; // Assume that it is auto-generated using a sequence. 

   @Column(name="field1")
   private String field1;


   private SecureString field2; //SecureString is a custom class

   //getters and setters

}

My delete method accepts an Entity.

@Override
public void delete(Entity baseEntity) {

   em.remove(baseEntity); //em is entityManager
}

Whenever the delete method is invoked I want three things inside my delete method:

1) Fields of MyEntity that are of type SecureString

2) Column name of that particular field in DB (The field may or may not have @Column annotation)

3) The value of id field

Note that when the delete() method is invoked, we don't know for which entity it is invoked, it may be for MyEntity1, MyEntity2 etc.

I have tried doing something like below:

for (Field field : baseEntity.getClass().getFields()) {
    if (SecureString.class.isAssignableFrom(field.getType())) {
        // But the field doesn't have annotation @Column specified
        Column column = field.getAnnotation(Column.class);

        String columnName = column.name();
    }
}

But this will only work if the field has @Column annotation. Also it doesn't get me other two things that I need. Any ideas?

Aman
  • 425
  • 1
  • 5
  • 22
  • `Column name of that particular field in DB (The field may or may not have '@Column' annotation)..` Are you creating the database tables or allowing Hibernate to create them? If hibernate is creating and you have not mentioned the '@Column', the variable name is taken as the default for column name. – Kaustubh Kallianpur Sep 21 '17 at 04:56
  • @KaustubhKallianpur i am allowing hibernate to create the database tables. Some fields have '@Column' annotation and some don't. I agree with you on your last statement. I need to have a condition to check whether a particular field is annotated with '@Column' or not and based on that column name should be fetched. – Aman Sep 21 '17 at 05:04
  • Hibernate fetches an entire row and all columns of that perticular row, and stores it into the respective Entity. You can get the values of them variables by using the Getter methods provided in your Entity. – Kaustubh Kallianpur Sep 21 '17 at 05:28
  • And how to convert the Entity interface reference to MyEntity class (as I don't know that the instance is of MyEntity, so I can't perform a direct cast. It can be of MyEntity1, MyEntity2 etc.)? Once I have got the object, I still need to determine which of the attributes is on type SecureString and the get its value,how can I achieve that? Could you propose an answer? – Aman Sep 21 '17 at 05:52
  • (as I don't know that the instance is of MyEntity.) Do you mean you don't know which row you are trying to fetch? – Kaustubh Kallianpur Sep 21 '17 at 06:21
  • @KaustubhKallianpur "Hibernate fetches an entire row and all columns" - no, columns are explicitly listed in query, i.e. never occur `select *` – Jacek Cz Sep 21 '17 at 08:47
  • @KaustubhKallianpur I meant that the delete() method is accepting an Interface, so inside the method we don't know for which class it is invoked. I have around 100 entities in my application and all of them are implementing this interface. – Aman Sep 21 '17 at 09:28
  • Question very interesting (for me). I have personal solution alike hibernate based answer above. Does exist portable, pure JPA answer? Possible is to write own reflection 'parser', reimplement all JPA rules, but this is reinvetiong wheel – Jacek Cz Sep 21 '17 at 09:44

1 Answers1

11

Hibernate can use different naming strategies to map property names, which are defined implicitly (without @Column(name = "...")). To have a 'physical' names you need to dive into Hibernate internals. First, you have to wire an EntityManagerFactory to your service.

@Autowired
private EntityManagerFactory entityManagerFactory;

Second, you have to retrieve an AbstractEntityPersister for your class

    SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
    AbstractEntityPersister persister = ((AbstractEntityPersister)sessionFactory.getClassMetadata(baseEntity.getClass()));

Third, you're almost there with your code. You just have to handle both cases - with and without @Column annotation. Try this:

for (Field field : baseEntity.getClass().getFields()) {
    if (SecureString.class.isAssignableFrom(field.getType())) {
        String columnName;
        if (field.isAnnotationPresent(Column.class)) {
            columnName = field.getAnnotation(Column.class).name();
        } else {
            String[] columnNames = persister.getPropertyColumnNames(field.getName());
            if (columnNames.length > 0) {
                columnName = columnNames[0];
            }
        }
    }
}

Note that getPropertyColumnNames() retrieves only 'property' fields, that are not a part of primary key. To retrieve key column names, use getKeyColumnNames().

And about id field. Do you really need to have all @Id's in child classes? Maybe would better to move @Id to Entity class and mark this class with @MappedSuperclass annotation? Then you can retrieve it just with baseEntity.getId();

Anatoly Shamov
  • 2,608
  • 1
  • 17
  • 27
  • 3
    `sessionFactory.getClassMetadata` is deprecated, it suggest to use `EntityManagerFactory.getMetamodel()`. Do you have any idea that how can we use it and retrieve the `AbstractEntityPersister`? – MJBZA Jun 05 '20 at 05:47
  • 4
    I found it: `((AbstractEntityPersister)((MetamodelImplementor) entityManager.getEntityManagerFactory().unwrap( SessionFactory.class).getMetamodel()).entityPersister(baseEntity.getClass()))` – MJBZA Jun 05 '20 at 05:58
  • 3
    Anyway your solution has a canonical problem! The `getFields` method only return public fields and normally columns in a JPA entity are `private`! We must use `getDeclaredFields` instead – MJBZA Jun 05 '20 at 06:27