Использовать абстрактный суперкласс в качестве параметра для хранилища данных Spring

Я знаю реализацию хранилища данных spring:

Создайте такой интерфейс:

public interface CountryRepository extends CrudRepository<Country, Long> {}

Теперь Country является AbstractCatalog, и у меня (много) больше каталогов в моем проекте.
Мне интересно, могу ли я сделать 1 репозиторий, который должен работать для всех каталогов:

public interface AbstractCatalogRepository extends CrudRepository<AbstractCatalog, Long> {}

Теперь с сохранением я не вижу непосредственно проблемы, но если я хочу найти AbstractCatalog, я уже уверен, что я ударил по стене, потому что репо не узнает, из какого объекта он должен выбрать.

AbstractCatalog.class

@MappedSuperclass
public abstract class AbstractCatalog extends PersistentEntity {

    /**
     * The Constant serialVersionUID.
     */
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    /**
     * The code.
     */
    @Column(unique = true, nullable = false, updatable = false)
    private String code;
    /**
     * The description.
     */
    @Column(nullable = false)
    private String description;
    /**
     * The in use.
     */
    @Column(name = "IN_USE", nullable = false, columnDefinition = "bit default 1")
    private Boolean inUse = Boolean.TRUE;

    // getters and setters
}

Country.class

@Entity
@Table(name = "tc_country")
@AttributeOverrides({
    @AttributeOverride(name = "id", column =
            @Column(name = "COUNTRY_SID")),
    @AttributeOverride(name = "code", column =
            @Column(name = "COUNTRY_CODE")),
    @AttributeOverride(name = "description", column =
            @Column(name = "COUNTRY_DESCRIPTION"))})
public class Country extends AbstractCatalog {

    public static final int MAX_CODE_LENGTH = 11;

    @Column(name = "GEONAMEID", nullable = true, unique = false)
    private Long geonameid;

    // getter and setter
}

Кто-нибудь знает, как я могу просто сделать 1 Repo для всех реализаций AbstractCatalog, не создавая один и тот же интерфейс снова и снова с минимальным изменением имени и класса реализации?

Ответы

Ответ 1

Если вы не используете наследование таблицы на стороне базы данных (например, таблицу суперкласса с столбец разборки), AFAIK, и на основе чтения учебник JPA, это невозможно сделать (т.е. просто использовать аннотацию @MappedSuperclass для вашего абстрактного класса)

Соответствующие суперклассы не могут быть запрошены и не могут использоваться в EntityManager или Query. Вы должны использовать сущности-подклассы сопоставленного суперкласса в EntityManager или Query-операциях. Связанные суперклассы не могут быть объектами отношений сущностей

Примечание. Абстракция репозитория JPA использует EntityManager под капотом. Я сделал простой тест и что вы получите (в случае реализации Hibernate) "IllegalArgumentException : not an entity AbstractClass"

С другой стороны, если вы используете наследование таблицы, вы можете использовать абстрактный тип. Я знаю, что вы сказали "с минимальными изменениями" (и, по-моему, мой короткий ответ - я не думаю, что это возможно - возможно, по причинам, которые вы догадались), поэтому я думаю, что остальная часть этого ответа для других вопрошающих умов; )

Примером стратегии наследования таблицы может быть что-то вроде этого (отказ от ответственности: это не правильная визуализация для наследования erd, но MySQL Workbench не поддерживает его, но то, что у меня ниже, спроектировало модель для MYSQL так, как она должна быть)

enter image description here

Где CountryCatalog имеет ссылку FK/PK на таблицу AbstractCatalog pk (id). Таблица AbstractCatalog имеет descriminatorColumn, которая будет использоваться для определения того, к какому подтипу относится супертип.

С точки зрения того, как вы бы это кодировали, он выглядел бы как

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name="descriminatorColumn")
@Table(name="AbstractCatalog")
public abstract class AbstractCatalog {
    @Id
    private long id;
    ...
}

@Entity
@Table(name = "CountryCatalog")
public class CountryCatalog extends AbstractCatalog {
    // id is inherited
    ...
}

public interface AbstractCatalogRepository 
                 extends JpaRepository<AbstractCatalog, Long> {

}

@Repository
public class CountryCatalogServiceImpl implements CountryCatalogService {

    @Autowired
    private AbstractCatalogRepository catalogRepository;

    @Override
    public List<CountryCatalog> findAll() {
        return (List<CountryCatalog>)(List<?>)catalogRepository.findAll();
    }

    @Override
    public CountryCatalog findOne(long id) {
        return (CountryCatalog)catalogRepository.findOne(id);
    }   
}

В принципе, в заключение, то, что вы пытаетесь сделать, не будет работать, если у вас нет наследования таблиц. Тип класса для репозитория должен быть сущностью. Если ваши таблицы не настроены таким образом для наследования, просто сводится к тому, хотите ли вы изменить таблицы. Это может быть немного, чтобы избежать нескольких хранилищ.

Некоторые ссылки, которые я использовал, здесь и здесь

Примечание. Все в этом ответе проверяется против поставщика Hibernate

Ответ 2

Oke, новый проект, и я немного подхожу к этой настройке.
Проблема заключалась в следующем: Мы хотим добавить вложения, но вложение может быть загружено с помощью файла, ссылки или почты.

Классы Pojo:

Attachment.java:

@Entity
@Table(name = "T_ATTACHMENT")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING)
public abstract class Attachment {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ATTACHMENT_SID")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "TASK_SID", referencedColumnName = "TASK_SID", nullable = false, unique = false, insertable = true, updatable = true)
    private Task task;

    @ManyToOne
    @JoinColumn(name = "USER_SID", referencedColumnName = "USER_SID", nullable = false, unique = false, insertable = true, updatable = true)
    private User user;

    public Task getTask() {
        return task;
    }

    public void setTask(Task task) {
        this.task = task;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

FileAttachment.java:

@Entity
@Table(name = "T_FILE_ATTACHMENT")
@DiscriminatorValue("FILE")
public class FileAttachment extends Attachment {

    @Column(name = "NAME", nullable = false, unique = false)
    private String fileName;

    @Lob
    @Basic
    @Column(name = "FILE", nullable = false, unique = false)
    private byte[] file;

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public byte[] getFile() {
        return file;
    }

    public void setFile(byte[] file) {
        this.file = file;
    }
}

MailAttachment.java:

@Entity
@Table(name = "T_MAIL_ATTACHMENT")
@DiscriminatorValue("MAIL")
public class MailAttachment extends Attachment {

    @Column(name = "RECIPIENT", nullable = false, unique = false)
    private String to;
    @Column(name = "CC", nullable = true, unique = false)
    private String cc;
    @Column(name = "BCC", nullable = true, unique = false)
    private String bcc;
    @Column(name = "TITLE", nullable = true, unique = false)
    private String title;
    @Column(name = "MESSAGE", nullable = true, unique = false)
    private String message;

    public String getTo() {
        return to;
    }

    public void setTo(String to) {
        this.to = to;
    }

    public String getCc() {
        return cc;
    }

    public void setCc(String cc) {
        this.cc = cc;
    }

    public String getBcc() {
        return bcc;
    }

    public void setBcc(String bcc) {
        this.bcc = bcc;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

LinkAttachment.java:

@Entity
@Table(name = "T_LINK_ATTACHMENT")
@DiscriminatorValue("LINK")
public class LinkAttachment extends Attachment {

    @Column(name = "DESCRIPTION", nullable = true, unique = false)
    private String description;

    @Column(name = "LINK", nullable = false, unique = false)
    private String link;

    public String getDescription() {
        return description == null ? getLink() : description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }
}

Spring репо данных:

AttachmentRepository.java:

public interface AttachmentRepository extends CustomRepository<Attachment, Long> {    
    List<Attachment> findByTask(Task task);
}

CustomRepository.java:

public interface CustomRepository<E, PK extends Serializable> extends
                PagingAndSortingRepository<E, PK>,
                JpaSpecificationExecutor<E>, 
                QueryDslPredicateExecutor<E> {
    @Override
    List<E> findAll();
}

И наконец сервис:

@Service
public class AttachmentServiceImpl implements AttachmentService {

    @Inject
    private AttachmentRepository attachmentRepository;

    @Override
    public List<Attachment> findByTask(Task task) {
        return attachmentRepository.findByTask(task);
    }

    @Override
    @Transactional
    public Attachment save(Attachment attachment) {
        return attachmentRepository.save(attachment);
    }
}

В результате:

Я могу сохранить абстрактное репо с любой реализованной мной реализацией, JPA сделает это правильно.

Если я вызываю findByTask(Task task), я получаю List<Attachment> всех подклассов, и у них есть правильный подкласс в обратном направлении.
Это означает, что вы можете сделать рендерера, который делает instanceof, и вы можете настроить рендеринг для каждого подкласса.

Даунсайд, вам все равно нужно создать настраиваемый репозиторий , но только если вы хотите запросить конкретное свойство, что находится в подклассе, или когда вам нужно только 1 конкретная реализация вместо всех реализации.

Ответ 3

Какая БД вы используете?

Если это JPA, взгляните на Можно ли использовать общий репозиторий для всех дочерних элементов MappedSuperClass с помощью Spring данных JPA?

Если это Mongo, вам нужно правильно настроить конфигурацию полиморфизма Джексона http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

Так что это возможно.