How to create a generic entity model class that supports generic id including auto generated ids?

限于喜欢 提交于 2019-11-30 09:37:33

You can "workaround" this forcing derived class to implement method which will ensure the Id is assigned and annotate this method with @PrePersist. You can provide default implementation for classes for which the Id will be auto generated.

Somethig like:

@MappedSuperclass
public abstract class BaseEntity<T> implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    protected T id;

    @PrePersist
    public void ensureIdAssigned() {
          ensureIdAssignedInternal();  
    }


    public abstract void ensureIdAssignedInternal();  
}


@MappedSuperclass
public abstract class AutoIdMaintaintedEntity<T> extends MaintainedEntity<T> { // provide default implementation for Entities with Id generated by @GeneratedValue(strategy=GenerationType.IDENTITY) on BaseEntity superclass
    public void ensureIdAssignedInternal() {
        // nothing here since the Id will be automatically assigned
    }
}

@Entity
public class Table1 extends AutoIdMaintaintedEntity<Long> {
    @Column
    private String value;
}

@Entity
public class Table2 extends BaseEntity<String> {
    @Column
    private String shortDescription;
    @Column
    private String longDescription;

    public void ensureIdAssignedInternal() {
         this.id = generateMyTextId();

    }

     private String generateMyTextId() {
         return "text id";
     }


}

Did not try this, but according to Hibernate's api this should not be complicated by creating custom implementation of IdentityGenerator.

It's generate method gets and object for which you are generating the value so you can check the type of the id field and return appropriate value for your primary key.

public class DynamicGenerator  implements IdentityGenerator

        public Serializable generate(SessionImplementor session, Object object)
                throws HibernateException {

             if (shouldUseAutoincrementStartegy(object)) { // basing on object detect if this should be autoincrement or not, for example inspect the type of id field by using reflection - if the type is Integer use IdentityGenerator, otherwise another generator 
                 return new IdentityGenerator().generate(seession, object)
             } else { // else if (shouldUseTextKey)

                 String textKey = generateKey(session, object); // generate key for your object

                 // you can of course connect to database here and execute statements if you need:
                 // Connection connection = session.connection();
                 //  PreparedStatement ps = connection.prepareStatement("SELECT nextkey from text_keys_table");
                 // (...)

                 return textKey;

            }

        }
    }

Having this simply use it as your generation strategy:

@MappedSuperclass
public abstract class BaseEntity<T> implements Serializable {
    @Id
    @GenericGenerator(name="seq_id", strategy="my.package.DynamicGenerator")
    protected T id;
}

For Hibernate 4, you should implement IdentifierGenerator interface.


As above is accepted for Hibernate it should be still possible to create it in more generic way for any "jpa compliant" provider. According to JPA api in GeneratedValue annotation you can provide your custom generator. This means that you can provide the name of your custom generator and you should implement this generator for each jpa provider.

This would mean you need to annotate BaseEntity with following annotation

@MappedSuperclass
public abstract class BaseEntity<T> implements Serializable {
    @Id
    @GeneratedValue(generator="my-custom-generator")
    protected T id;
}

Now you need to register custom generator with name "my-custom-generator" for each jpa provider you would like to use.

For Hibernate this is surly done by @GenericGenerator annotation as shown before (adding @GenericGenerator(name="my-custom-generator", strategy="my.package.DynamicGenerator" to BaseEntity class on either id field or BaseEntity class level should be sufficient).

In EclipseLink I see that you can do this via GeneratedValue annotation and registering it via SessionCustomizer:

            properties.put(PersistenceUnitProperties.SESSION_CUSTOMIZER,
                    "my.custom.CustomIdGenerator");

public class CustomIdGenerator extends Sequence implements SessionCustomizer {


    @Override
    public Object getGeneratedValue(Accessor accessor,
            AbstractSession writeSession, String seqName) {
        return  "Id"; // generate the id
    }

    @Override
    public Vector getGeneratedVector(Accessor accessor,
            AbstractSession writeSession, String seqName, int size) {
        return null;
    }

    @Override
    protected void onConnect() {
    }

    @Override
    protected void onDisconnect() {
    }

    @Override
    public boolean shouldAcquireValueAfterInsert() {
        return false;
    }

    @Override
    public boolean shouldOverrideExistingValue(String seqName,
            Object existingValue) {
        return ((String) existingValue).isEmpty();
    }

    @Override
    public boolean shouldUseTransaction() {
        return false;
    }

    @Override
    public boolean shouldUsePreallocation() {
        return false;
    }

    public void customize(Session session) throws Exception {
        CustomIdGenerator sequence = new CustomIdGenerator ("my-custom-generator");

        session.getLogin().addSequence(sequence);
    }

}    

Each provider must give a way to register id generator, so you would need to implement and register custom generation strategy for each of the provider if you want to support all of them.

Inheritance hierarchies fight ORM. So keep things simple and stay a little closer to the database implementation. Don't map a hierarchy of abstract superclasses for this, but embed annotated POJO's for chunks of shared columns. They may well turn out to be handy to work with in the rest of your code as well.

Create @Embeddable classes for the shared fields, and make the class for your composite ID @Embeddable too.

@Embeddable
public class Maintained implements Serializable{
    private String maintainedBy;
    private String updatedBy;
    // getters and setters
}

@Embeddable
public class CompositeId implements Serializable{
    @Column
    private int id1;
    @Column
    private int id2;
    ...
}

The simplest version of your implementation classes then look like this:

@Entity
public class Table1 {
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Id
    protected Long id;
    @Version
    private String version;
    @Embedded
    private Maintained maintained;
    ...
    public Maintained getMaintained(){
        return maintained;
    }
}

For the String ID, no auto generation:

@Entity
public class Table2 {
    @Id
    private String id;
    @Column
    private String shortDescription;
    @Column
    private String longDescription;
    ...
}

And the composite ID as @EmbeddedId:

@Entity
public class Table3 { 
    @EmbeddedId 
    private CompositeId id;
    @Version
    private String version;
    @Column
    private int amount;
    ...
}

As an extra benefit, you can mix and match adding more of these traits if you like, since you're no longer constrained by the inheritance tree.

(But you can keep the hierarchy in place containing getters and setters and default delegates if existing code relies on it and/or benefits from it.)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!