JPA: Как получить сущность на основе значения поля, отличного от ID?

В JPA (Hibernate), когда мы автоматически генерируем поле ID, предполагается, что пользователь не знает об этом ключе. Таким образом, при получении объекта пользователь запрашивает на основе некоторого поля, отличного от ID. Как мы можем получить объект в этом случае (поскольку em.find() не может использоваться).

Я понимаю, что мы можем использовать запрос и позже фильтровать результаты. Но есть ли более прямой путь (потому что это очень распространенная проблема, как я понимаю).

Ответ 1

Это не "проблема", как вы ее заявили.

Hibernate имеет встроенный find(), но вам нужно создать собственный запрос, чтобы получить конкретный объект. Я рекомендую использовать Hibernate Criteria:

Criteria criteria = session.createCriteria(YourClass.class);
YourObject yourObject = criteria.add(Restrictions.eq("yourField", yourFieldValue))
                             .uniqueResult();

Это создаст Criteria в вашем текущем классе, добавив ограничение на то, что столбец "yourField" равен значению yourFieldValue. uniqueResult() говорит, что он приносит уникальный результат. Если все объекты совпадают, вы должны получить список.

List<YourObject> list = criteria.add(Restrictions.eq("yourField", yourFieldValue)).list();

Если у вас есть дополнительные вопросы, пожалуйста, не стесняйтесь спрашивать. Надеюсь, это поможет.

Ответ 2

если у вас есть хранилище для сущности Foo и вам нужно выбрать все записи с точным строковым значением boo (также работает для других примитивных типов или типов сущностей). Поместите это в интерфейс вашего репозитория:

List<Foo> findByBoo(String boo);

если вам нужно заказать результаты:

List<Foo> findByBooOrderById(String boo);

Подробнее смотрите по ссылке.

Ответ 3

В принципе, вы должны добавить определенное уникальное поле. Обычно я использую поля xxxUri.

class User {

    @Id
    // automatically generated
    private Long id;

    // globally unique id
    @Column(name = "SCN", nullable = false, unique = true)
    private String scn;
}

И ваш бизнес-метод будет работать следующим образом.

public User findUserByScn(@NotNull final String scn) {
    CriteriaBuilder builder = manager.getCriteriaBuilder();
    CriteriaQuery<User> criteria = builder.createQuery(User.class);
    Root<User> from = criteria.from(User.class);
    criteria.select(from);
    criteria.where(builder.equal(from.get(User_.scn), scn));
    TypedQuery<User> typed = manager.createQuery(criteria);
    try {
        return typed.getSingleResult();
    } catch (final NoResultException nre) {
        return null;
    }
}

Ответ 4

Лучшая практика - использование аннотации @NaturalId. Он может использоваться в качестве бизнес-ключа для некоторых случаев, когда он слишком сложный, поэтому некоторые поля используются в качестве идентификатора в реальном мире. Например, у меня есть пользовательский класс с идентификатором пользователя в качестве первичного ключа, а электронная почта также является уникальным полем. Таким образом, мы можем использовать электронную почту как наш естественный id

@Entity
@Table(name="user")
public class User {
  @Id
  @Column(name="id")
  private int id;

  @NaturalId
  @Column(name="email")
  private String email;

  @Column(name="name")
  private String name;
}

Чтобы получить нашу запись, просто используйте "session.byNaturalId()"

Session session = sessionFactory.getCurrentSession();
User user = session.byNaturalId(User.class)
                   .using("email","[email protected]")
                   .load()

Ответ 5

Напишите собственный метод, подобный этому:

public Object findByYourField(Class entityClass, String yourFieldValue)
{
    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Object> criteriaQuery = criteriaBuilder.createQuery(entityClass);
    Root<Object> root = criteriaQuery.from(entityClass);
    criteriaQuery.select(root);

    ParameterExpression<String> params = criteriaBuilder.parameter(String.class);
    criteriaQuery.where(criteriaBuilder.equal(root.get("yourField"), params));

    TypedQuery<Object> query = entityManager.createQuery(criteriaQuery);
    query.setParameter(params, yourFieldValue);

    List<Object> queryResult = query.getResultList();

    Object returnObject = null;

    if (CollectionUtils.isNotEmpty(queryResult)) {
        returnObject = queryResult.get(0);
    }

    return returnObject;
}

Ответ 6

Это решение из книги "Начало гибернации":

     Query<User> query = session.createQuery("from User u where u.scn=:scn", User.class);
     query.setParameter("scn", scn);
     User user = query.uniqueResult();    

Ответ 8

Я написал библиотеку, которая помогает делать именно это. Он позволяет выполнять поиск по объекту, просто инициализируя поля, которые вы хотите фильтровать, с помощью: https://github.com/kg6zvp/GenericEntityEJB

Ответ 9

В моем приложении Spring Boot я решил похожую проблему:

@Autowired
private EntityManager entityManager;

public User findByEmail(String email) {
    User user = null;
    Query query = entityManager.createQuery("SELECT u FROM User u WHERE u.email=:email");
    query.setParameter("email", email);
    try {
        user = (User) query.getSingleResult();
    } catch (Exception e) {
        // Handle exception
    }
    return user;
}

Ответ 10

Обратитесь - Spring документы для методов запроса

Мы можем добавить методы в Spring Jpa, передав параметры diff в такие методы, как:
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

// Enabling static ORDER BY for a query 

List<Person> findByLastnameOrderByFirstnameAsc(String lastname);

Ответ 11

Редактировать: только что понял, что @Chinmoy добился в основном то же самое, но я думаю, что я, возможно, сделал лучшую работу ELI5 :)

Если вы используете разновидность Spring Data, чтобы помочь сохранить/извлечь вещи из любого типа Repository, который вы определили, вы, вероятно, можете попросить вашего провайдера JPA сделать это за вас с помощью некоторых хитрых приемов с именами методов в Repository. ] интерфейсный класс. Позвольте мне объяснить.

(Как выражение об отказе от ответственности, я всего несколько минут назад сделал/все еще выясняю это для себя.)

Например, если я храню токены в своей базе данных, у меня может быть класс сущности, который выглядит следующим образом:

@Data // << Project Lombok convenience annotation
@Entity
public class Token {
    @Id
    @Column(name = "TOKEN_ID")
    private String tokenId;

    @Column(name = "TOKEN")
    private String token;

    @Column(name = "EXPIRATION")
    private String expiration;

    @Column(name = "SCOPE")
    private String scope;
}

И у меня, вероятно, есть такой интерфейс CrudRepository<K,V>, который позволяет мне бесплатно выполнять простые операции CRUD в этом репозитории.

@Repository
// CrudRepository<{Entity Type}, {Entity Primary Key Type}>
public interface TokenRepository extends CrudRepository<Token, String> { }

И когда я ищу один из этих токенов, моей целью может быть проверка, например, срока действия или объема. В любом из этих случаев у меня, вероятно, нет удобного tokenId, а просто значение самого поля token, которое я хочу найти.

Для этого вы можете добавить дополнительный метод к интерфейсу TokenRepository, чтобы сообщить своему провайдеру JPA, что значение, которое вы передаете методу, - это не tokenId, а значение другого поля внутри класс Entity, и он должен учитывать это при генерации фактического SQL, который он будет запускать в вашей базе данных.

@Repository
// CrudRepository<{Entity Type}, {Entity Primary Key Type}>
public interface TokenRepository extends CrudRepository<Token, String> { 
    List<Token> findByToken(String token);
}

Я читал об этом на странице документации Spring Data R2DBC, и до сих пор он работает в приложении SpringBoot 2.x, хранящемся во встроенной базе данных H2.

Ответ 12

Использование запросов CrudRepository и JPA работает для меня:

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;

public interface TokenCrudRepository extends CrudRepository<Token, Integer> {

 /**
 * Finds a token by using the user as a search criteria.
 * @param user
 * @return  A token element matching with the given user.
 */
    @Query("SELECT t FROM Token t WHERE LOWER(t.user) = LOWER(:user)")
    public Token find(@Param("user") String user);

}

и вы вызываете метод find find следующим образом:

public void destroyCurrentToken(String user){
    AbstractApplicationContext context = getContext();

    repository = context.getBean(TokenCrudRepository.class);

    Token token = ((TokenCrudRepository) repository).find(user);

    int idToken = token.getId();

    repository.delete(idToken);

    context.close();
}