79

在 JPA(Hibernate)中,当我们自动生成 ID 字段时,假设用户不知道该键。因此,在获取实体时,用户会根据 ID 以外的某些字段进行查询。在那种情况下我们如何获得实体(因为em.find()不能使用)。

我知道我们可以稍后使用查询并过滤结果。但是,有没有更直接的方法(因为据我所知,这是一个非常普遍的问题)。

4

15 回答 15

44

正如您所说,这不是一个“问题”。

Hibernate 具有内置的find(),但您必须构建自己的查询才能获取特定对象。我推荐使用HibernateCriteria

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

这将criteria在您当前的类上创建一个,添加列“yourField”等于 value 的限制yourFieldValueuniqueResult()告诉它带来独特的结果。如果有更多对象匹配,您应该检索一个列表。

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

如果您还有其他问题,请随时提问。希望这可以帮助。

于 2013-02-20T10:09:07.673 回答
31

如果您有实体存储库Foo并且需要选择具有确切字符串值的所有条目boo(也适用于其他原始类型或实体类型)。将其放入您的存储库界面:

List<Foo> findByBoo(String boo);

如果您需要订购结果:

List<Foo> findByBooOrderById(String boo);

在参考中查看更多信息。

于 2016-02-03T14:33:01.523 回答
10

基本上,您应该添加一个特定的唯一字段。我通常使用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;
    }
}
于 2016-07-13T04:14:50.147 回答
7

最佳实践是使用 @NaturalId 注释。它可以用作某些情况下的业务密钥它太复杂了,因此某些字段在现实世界中用作标识符。例如,我有以用户 ID 作为主键的用户类,电子邮件也是唯一字段。所以我们可以使用email作为我们的自然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","huchenhai@qq.com")
                   .load()
于 2017-12-20T20:56:59.373 回答
6

此解决方案来自《Beginning Hibernate》一书:

     Query<User> query = session.createQuery("from User u where u.scn=:scn", User.class);
     query.setParameter("scn", scn);
     User user = query.uniqueResult();    
于 2019-09-12T06:15:10.720 回答
4

编写一个这样的自定义方法:

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;
}
于 2013-02-20T10:36:21.630 回答
3

我解决了一个类似的问题,我想通过它的 isbnCode 而不是你的 id(主键)找到一本书。

@Entity
public class Book implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String isbnCode;

...

在存储库中,该方法的创建就像提到的@kamalveer singh 一样。请注意,方法名称是 findBy+fieldName(在我的例子中:findByisbnCode):

@Repository
public interface BookRepository extends JpaRepository<Book, Integer> {

    Book findByisbnCode(String isbnCode);
}

然后,在服务中实现该方法:

@Service
public class BookService {

    @Autowired
    private BookRepository repo;
    
    
    public Book findByIsbnCode(String isbnCode) {
        Book obj = repo.findByisbnCode(isbnCode);
        return obj; 
    }
}
于 2020-09-15T18:08:17.163 回答
1

All the answers require you to write some sort of SQL/HQL/whatever. Why? You don't have to - just use CriteriaBuilder:

Person.java:

@Entity
class Person  {
  @Id @GeneratedValue
  private int id;

  @Column(name = "name")
  private String name;
  @Column(name = "age")
  private int age;
  ...
}

Dao.java:

public class Dao  {
  public static Person getPersonByName(String name)  {
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        
        CriteriaBuilder cb = session.getCriteriaBuilder();
        
        CriteriaQuery<Person> cr = cb.createQuery(Person.class);
        Root<Person> root = cr.from(Person.class);
        cr.select(root).where(cb.equal(root.get("name"), name));  //here you pass a class field, not a table column (in this example they are called the same)

        Query query = session.createQuery(cr);
        query.setMaxResults(1);
        List<Person> resultList = query.getResultList();

        Person result = resultList.get(0);
        
        return result;
  }
}

example of use:

public static void main(String[] args)  {
  Person person = Dao.getPersonByName("John");
  System.out.println(person.getAge());  //John's age
}
于 2019-12-16T17:51:04.017 回答
1

编辑:刚刚意识到@Chinmoy 基本上是在做同样的事情,但我想我可能做得更好 ELI5 :)

如果您使用 Spring Data 的风格来帮助从Repository您定义的任何类型中持久化/获取内容,您可能可以让您的 JPA 提供程序通过Repository接口类中的方法名称的一些巧妙技巧为您执行此操作。请允许我解释一下。

(作为免责声明,我刚才确实/仍在为自己解决这个问题。)

例如,如果我将 Tokens 存储在我的数据库中,我可能有一个如下所示的实体类:

@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 文档页面上读到了这一点,到目前为止,它似乎在存储在嵌入式 H2 数据库中的 SpringBoot 2.x 应用程序中工作。

于 2019-09-06T21:25:47.093 回答
1

不,您不需要进行条件查询,如果您在 Spring-boot 中工作,您只需做简单的事情即可:在您的 repo 中使用 findBy [exact field name] 声明一个方法名称。示例 - 如果您的模型或文档包含一个字符串字段myField 并且您想通过它查找,那么您的方法名称将是:

findBymyField(String myField);

于 2020-05-01T08:32:14.080 回答
0

看一下:

于 2013-02-20T10:07:54.263 回答
0

在我的 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;
}
于 2019-02-04T21:29:34.760 回答
0

我写了一个库来帮助做到这一点。它允许通过仅初始化您想要过滤的字段来按对象搜索:https ://github.com/kg6zvp/GenericEntityEJB

于 2016-06-13T08:49:53.750 回答
0

参考 -查询方法的 Spring 文档

我们可以通过在方法中传递差异参数来在 Spring Jpa 中添加方法,例如:
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

// Enabling static ORDER BY for a query 

List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
于 2019-03-11T04:25:19.843 回答
-2

使用 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 自定义方法:

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();
}
于 2014-03-24T23:03:08.933 回答