web-dev-qa-db-fra.com

Utiliser la super classe abstraite comme paramètre pour le référentiel de données Spring

Je connais l'implémentation du référentiel de données du printemps:

Créez une interface comme celle-ci:

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

Maintenant, Country est un AbstractCatalog et j'ai (beaucoup) plus de catalogues dans mon projet.
Je me demande si je peux créer un référentiel qui devrait fonctionner pour tous les catalogues:

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

Maintenant, avec save, je ne vois pas directement de problème, mais si je veux rechercher un AbstractCatalog, je suis déjà sûr que je vais toucher le mur car le repo ne saura pas de quel objet il doit choisir.

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
}

Quelqu'un at-il une idée de comment je pourrais simplement faire 1 Repo pour toutes les implémentations de AbstractCatalog sans créer la même interface encore et encore avec juste le changement minimal de nom et de classe d'implémentation?

20
chillworld

Si vous n'utilisez pas l'héritage de table du côté de la base de données (par exemple, table de super classe avec colonne descriminateur ), AFAIK, et basé sur la lecture de tutoriel JPA , cela ne peut pas être fait (c'est-à-dire en utilisant simplement @MappedSuperclass annotation pour votre classe abstraite)

Les superclasses mappées ne peuvent pas être interrogées et ne peuvent pas être utilisées dans les opérations EntityManager ou Query. Vous devez utiliser les sous-classes d'entités de la superclasse mappée dans les opérations EntityManager ou Query. Les superclasses mappées ne peuvent pas être des cibles de relations d'entité

Remarque, l'abstraction du référentiel JPA utilise un EntityManager sous le capot. J'ai fait un test simple, et ce que vous obtiendrez (dans le cas de l'implémentation d'Hibernate) un "IllegalArgumentException : not an entity AbstractClass "

D'un autre côté, si vous faites utilisez l'héritage de table, alors vous pouvez utiliser le type abstrait. Je sais que vous avez dit "avec juste le changement minimal" (et je suppose que ma réponse courte est que je ne pense pas que ce soit possible - probablement pour les raisons que vous avez devinées), donc je suppose que le reste de cette réponse est pour les autres esprits curieux ;-)

Un exemple de stratégie d'héritage de table serait quelque chose comme ceci (avertissement: ce n'est pas le visualisation correcte pour l'héritage erd, mais MySQL Workbench ne le prend pas en charge, mais ce que j'ai ci-dessous avant a conçu le modèle à MYSQL comme il faut)

enter image description here

CountryCatalog a une référence FK/PK à la table AbstractCatalog pk (id). La table AbstractCatalog a un descriminatorColumn qui sera utilisé pour déterminer à quel sous-type l'occurrence de supertype est liée.

En termes de codage, cela ressemblerait à quelque chose comme

@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);
    }   
}

Fondamentalement, en conclusion, ce que vous essayez de faire ne fonctionnera pas si vous n'avez pas d'héritage de table. Le type de classe du référentiel doit être une entité. Si vos tables ne sont pas configurées de cette façon pour l'héritage, il s'agit simplement de savoir si vous souhaitez ou non modifier les tables. Cependant, cela peut être un peu trop pour éviter plusieurs référentiels.

Certaines références que j'ai utilisées sont ici et ici

Remarque: Tout dans cette réponse est testé par rapport au fournisseur Hibernate

14
Paul Samsotha

Oke, nouveau projet et je suis un peu en train de suivre cette configuration.
Le problème était: Nous voulons ajouter des pièces jointes, mais une pièce jointe peut être en train de télécharger un fichier, un lien ou un courrier.

Cours de 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;
    }
}

Repo de données de printemps:

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();
}

Et enfin le service:

@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);
    }
}

Il en résulte:

Je peux enregistrer dans le référentiel abstrait avec n'importe quelle implémentation que j'ai créée, JPA le fera correctement.

Si j'appelle findByTask(Task task) j'obtiens un List<Attachment> De toutes les sous-classes, et elles ont la bonne sous-classe à l'arrière.
Cela signifie que vous pouvez créer un moteur de rendu qui fait instanceof et vous pouvez personnaliser votre rendu pour chaque sous-classe.

L'inconvénient est que vous devez toujours créer des référentiels spécifiques personnalisés, mais uniquement lorsque vous souhaitez interroger sur une propriété spécifique ce qui se trouve dans la sous-classe ou lorsque vous ne souhaitez qu'une seule implémentation spécifique au lieu de toutes les implémentations.

5
chillworld

Quelle base de données utilisez-vous?

Si c'est JPA, jetez un œil à Puis-je utiliser un référentiel générique pour tous les enfants d'une MappedSuperClass avec Spring Data JPA?

Si c'est Mongo, vous devez régler correctement la configuration du polymorphisme de Jackson http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

C'est donc possible.

1
mavarazy