0

What's the best practice to create persistence (say via Spring Boot or just JPA or Hibernate itself) for a data model coming from a non-modifiable dependency? Typical limitations like not being able to override a field or what patterns like Decorator allow and what not slowed my progress down. I tried some things, but I always end up with the result that it would be necessary to either modify the source model (like adding annotations to make it natively compatible -> the fork I don't want) OR write a ton of wrapper code which would replicate the original model too much - but even this isn't working right now:

I tried

  • Creating a JpaRepository for the original class. Doesn't work, because casting the extended class to its parent class is not working.
  • Extend the original class with a custom class that gets necessary annotations like @Entity can be used in such a repository. But problems here were
    • that the original class is missing an @Id annotation, which could be fixed by using a new ID in the extended class, but
    • the given model also has a non-simple architecture, including lists of other classes that are part of the model itself. So other annotations like @ElementCollection might be necessary, which can't be added because overriding of fields is not possible.
      • Hiding it with creating a new field with the same name in the new class is not working:
      • An error like Could not determine type for: java.util.List, at table: yeah_this_one, for columns:[org.hibernate.mapping.Column(objects)] indicates that the original field can't be hidden completely (changed table and column name in new class to verify that).
      • So of course adding @ElementCollection (which is said to solve that) isn't helping here, too.
    • @AttributeOverride is also not working to override annotations to set the ID or other settings, only the name and column can be changed.

I'm stuck at this state and am wondering if this is even the right approach at all.

The setup or what I would expect to work from my understanding:

The general idea is based on this Spring Boot REST tutorial, which I tried to expand with a model from a dependency.

Let's assume there is the original model class Model from a dependency that can not be modified. The ModelEntity would be the extended class to act as way to pull the model into Spring persistence.

In the scope of the dependency the original class would be like:

// Given dependency, not modifiable
@Some existing annotation
public class Model extends AnotherClassFromDep {

    @more annotations
    private IdLikeClassFromDep modelId;

    //more complex attribute
    @Nullable
    private List<RefClassFromDep> objects = new ArrayList<>();

    // more attributes, getter, setter etc.
}

In the scope of my program:

In combination with this little additional orm.xml it is possible to annotate the original Model as MappedSuperclass without modifying it (according to https://stackoverflow.com/a/2516951/1844976).

<?xml version="1.0" encoding="UTF-8"?>

<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" version="1.0">
    <mapped-superclass class="package.name.of.original.Model">

    </mapped-superclass>
</entity-mappings>

This allows to create a class like this, which extends the original POJO model to add JPA annotations:

@Entity
public class ModelEntity extends Model {

    // some @Id attribute is necessary, which should correspond to
    // the already existing ID attribute from the original `Model`
    // in the best case, but an additional one would work too
    private @Id @GeneratedValue Long id;

    // Different approaches to solve the List error from above, for
    // instance hiding the original attribute
    @ElementCollection
    private List<RefClassFromDep> objects;

    public ModelEntity(){
        super();
    }

}

At the current state the issues are blocking me from going further. But, altogether I would expect this to work with a JpaRepository:

// of course, creating a JpaRepository with original `Model` wouldn't
// work, because it has no `@Entity`
public interface ModelRepository extends JpaRepository<ModelEntity, IdLikeClassFromDep> {
}

In a way that actually accessing it like that is possible:

@Configuration
public class LoadDatabase {

    @Bean
    CommandLineRunner initDatabase(ModelRepository modelRepository) {
        return args -> {
            // depending on the implementation above, either create a 
            // Model and cast it or directly create a ModelEntity, set
            // attriubtes and save it through the JpaRepository
            modelRepository.save(model);
        };
    }
}

Both more abstract and specific code-related ideas and comments would help me. Thanks!

J.S.
  • 63
  • 1
  • 5
  • I'm not sure 100%, but you can extend base class and add all JPA related annotations to getters. – Bor Laze May 07 '19 at 18:31
  • 1
    How about creating separate JPA entity classes and then map them to the model you need with Dozer or ModelMapper? – Oleg May 07 '19 at 18:31

1 Answers1

1

In the old days, Jpa/Hibernate were configured via XML. You needed to provide persistence.xml for general configuration. In this file, you added <mapping-file> tag pointing to another file orm.xml In this file you configured mapping for your entities (which is done via JPA annotations these days).

See https://vladmihalcea.com/how-to-use-external-xml-mappings-files-outside-of-jar-with-jpa-and-hibernate/

While the methods described above are considered legacy, they are still supported. LocalContainerEntityManagerFactoryBean has method setMappingResources allowing you to point to the orm.xml file. There is some funkiness about search paths and default locations, but it is well documented: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.html#setMappingResources-java.lang.String...-

Note that the third-party class you are configuring this way needs to conform to Java Beans conventions (no-args constructor, getters and setters)

Lesiak
  • 22,088
  • 2
  • 41
  • 65