it-swarm.com.ru

Спецификация API критериев JPA для многих ко многим

У меня есть три класса, как указано ниже. Я пытаюсь создать спецификацию для фильтрации данных, где есть совпадения в связанной таблице.

public class Album {
    private Long id;
    private List<AlbumTag> albumTags;
}

public class Tag {
    private Long id;
    private String category;
}

public class AlbumTag{
    private Long id;
    private Album album;
    private Tag tag;
}

В приведенной выше схеме я пытаюсь найти список всех альбомов из таблицы Album со ссылкой в ​​AlbumTag. SQL, которого я хочу достичь, не должен быть таким же, ниже

select *
from Album A 
where (A.Id in (select [AT].AlbumId 
from AlbumTag [AT]))

То, что я пробовал до сих пор, что не работает, конечно, ниже

public class AlbumWithTagSpecification implements Specification<Album> {

    @Override
    public Predicate toPredicate(Root<Album> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {

         final Subquery<Long> personQuery = cq.subquery(Long.class); 
         final Root<Album> album = personQuery.from(Album.class); 
         final Join<Album, AlbumTag> albumTags = album.join("albumTags");
         personQuery.select((albumTags.get("album")).get("id"));
         personQuery.where(cb.equal(album.get("id"), (albumTags.get("album")).get("id"))); 
         return cb.in(root.get("id")).value(personQuery);

    }
}
8
Atul Chaudhary

Используя пружинную загрузку и пружинные данные JPA, вы можете предпочесть отношение сущностей для извлечения данных.

1. Аннотируйте класс домена с отношением объекта, которое дано ниже:

@Entity
@Table(name="Album")
public class Album {
    @Id
    @Column(name="id")
    private Long id;
    @OneToMany(targetEntity = AlbumTag.class, mappedBy = "album")
    private List<AlbumTag> albumTags;

    //getter and setter
}

@Entity
@Table(name="Tag")
public class Tag {
    @Id
    @Column(name="id")
    private Long id;
    @Column(name="category")
    private String category;

    //getter and setter
}

@Entity
@Table(name="AlbumTag")
public class AlbumTag{
    @Id
    @Column(name="id")
    private Long id;
    @ManyToOne(optional = false, targetEntity = Album.class)
    @JoinColumn(name = "id", referencedColumnName="id", insertable = false, updatable = false)
    private Album album;
    @ManyToOne(optional = false, targetEntity = Tag.class)
    @JoinColumn(name = "id", referencedColumnName="id", insertable = false, updatable = false)
    private Tag tag;

    //getter and setter
}

2. использовать данные пружины, чтобы получить подробную информацию, используя ниже:

Album album = ablumRepository.findOne(1); // get the complete details about individual album.
List<AlbumTag> albumTags = ablum.getAlbumTags(); // get the all related albumTags details for particular album.

Я надеюсь, что это поможет вам решить.

2
Praveen D

Это выглядит как классический пример для многих. Три класса, которые у вас есть, отображаются непосредственно в таблицы, которые вы ожидаете в базе данных. JPA - это библиотека объектно-реляционного сопоставления (ORM), которая означает, что мы можем структурировать классы в стиле OO и отобразить их в базовую реляционную базу данных.

Класс AlbumTag может быть опущен, а отношение @ManyToMany добавлено к Album и Tag.

public class Album {
    private Long id;

    @ManyToMany
    @JoinTable(name="AlbumTag",
        joinColumns=
            @JoinColumn(name="album", referencedColumnName="id"),
        inverseJoinColumns=
            @JoinColumn(name="tag", referencedColumnName="id"))
    private List<Tag> tags;
}

public class Tag {
    private Long id;
    private String category;

    @ManyToMany(mappedBy="tags")
    private List<Album> albums;
}

Чтобы найти альбомы с помощью Tag, сначала вы должны получить Tag из репозитория, используя что-то вроде findById(1l); или findByCategory("Rock");, а затем просто вызвать getAlbums() для объекта Tag.

Примечание. Одно небольшое отличие состоит в том, что таблица AlbumTag будет иметь только два столбца (альбом и тег). Дополнительный столбец идентификатора на AlbumTag не является необходимым, поскольку комбинация альбома и тега будет уникальным идентификатором, и вам никогда не потребуется искать по идентификатору в этой таблице в любом случае.

1
Ben Thurley
creteria query for join tables

CriteriaQuery<Album> query = cb.createQuery(Album.class);
Root<Album> album = query.from(Teacher.class);
Join<Album, AlbumTag> tag = teacher.join("id");
query.select(tag).where(cb.equal(album.get("album")));

List<Album> results = em.createQuery(query).getResultList();
for (Album al : results) {
    System.out.println("album-->+al.get(name));
}
1
Muthu

Так как вы используете spring-data-jpa, вы должны действительно воспользоваться возможностями, которые он предоставляет.

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

Во-вторых, вы должны пометить предложения вашей сущности:

@Entity
public class Album {
@Id
@Column
private Long id;
}

@Entity
public class Tag {
  @Id
  @Column
  private Long id;
  @Column
  private String category;
}

@Entity
@Table
public class AlbumTag{
  @Id
  @Column
  private Long id;
  @ManyToOne
  @JoinColumn
  private Album album;
  @ManyToOne
  @JoinColumn
  private Tag tag;
}

Затем вы должны создать репозитории для ваших классов сущностей.

interface AlbumRepository extends JpaRepository<Album, Long>{

   @Query
   ("select DISTINCT(a) from AlbumTag at "+
    "join at.album a "
    "where at.tag is not null")
   List<Album> findAlbumWithTag();
}

Затем просто вызовите функцию репозитория, которая вернет список альбомов, в которых есть хотя бы один тег.

1
Petru Scurtu

Ну, я бы не стал использовать операцию in в этом случае - это только усложняет запрос и спецификацию. Проблема, которую вы описали, на самом деле сводится к объединению записей из Table A со связанными записями из Table B, поэтому запрос в вашем случае будет выглядеть так:

SELECT a from Album a join AlbumTag at on a.id = at.albumId - по мере необходимости он вернет все альбомы с тегами альбома. Внутреннее соединение объяснил

Так что в вашем случае я бы создал этот «фабричный» метод, который бы создал для вас эту спецификацию.

public static Specification<Album> withTags() {
    return new Specification<Album>() {
        @Override
        public Predicate toPredicate(Root<Album> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            return root.join("albumTags").getOn();
    }
};

}

Также я бы посоветовал вам взглянуть на библиотеку static metamodel из hibernate - ссылка на введение . Он генерирует для вас статическую модель из ваших классов сущностей, которая помогает вам избежать создания запросов/спецификаций с использованием жестко закодированных строк.

1
bpawlowski

Подзапросы в JPA действительно работают только с CriteriaBuilder.exists (), поэтому я бы попробовал:

public Predicate toPredicate(Root<Album> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {

     final Subquery<Long> subQuery = cq.subquery(Long.class); 
     final Root<AlbumTag> albumTag = subQuery.from(AlbumTag.class); 
     // it doesn't really matter what we select
     subQuery.select(cb.literal(1));
     subQuery.where(cb.equal(root.get("id"), (albumTag.get("album")).get("id"))); 

     return cb.exists(subQuery);

}

что эквивалентно 

select *
from Album A 
where exists(
    select 1 from AlbumTag AT 
    where AT.AlbumId = A.Id
)
1
user158037