JPA: 如何根据字段值而不是 ID 来获取实体?

在 JPA (Hibernate)中,当我们自动生成 ID 字段时,假设用户不知道这个密钥。因此,当获取实体时,用户将基于 ID 以外的某个字段进行查询。在这种情况下,我们如何获得实体(因为不能使用 em.find())。

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

229055 次浏览

看看这个:

这不是你所说的“问题”。

Hibernate 具有内置的 find(),但是您必须构建自己的查询才能获得特定的对象。我推荐使用 冬眠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();

如果您有任何进一步的问题,请随时提问。希望这有所帮助。

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

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

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

如果您有实体 Foo的存储库,并且需要选择具有精确字符串值 boo的所有条目(也适用于其他基元类型或实体类型)。将其放入存储库接口:

List<Foo> findByBoo(String boo);

如果你需要订购结果:

List<Foo> findByBooOrderById(String boo);

详情请看 参考文献

我已经写了一个图书馆,帮助精确地做到这一点。它允许通过对象进行搜索,只需初始化要通过以下方式筛选的字段: https://github.com/kg6zvp/GenericEntityEJB

基本上,你应该添加一个特定的唯一字段。我通常使用 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;
}
}

最佳实践是使用@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()

在我的 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;
}

参考 -查询方法的 Spring 文档

我们可以在 Spring Jpa 中添加方法,方法是在以下方法中传递 diff params:
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

// Enabling static ORDER BY for a query


List<Person> findByLastnameOrderByFirstnameAsc(String lastname);

编辑: 刚刚意识到@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>接口,它为我提供了对该 Repository 的简单 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);
}

我在 SpringDataR2DBC 文档页上读到过这个,目前为止,它似乎是在一个存储在嵌入式 H2数据库中的 SpringBoot 2.x 应用程序中工作的。

这个解决方案来自于开始冬眠的书:

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

所有的答案都要求你写一些 SQL/HQL 或者别的什么。为什么? 你不必——只需使用 CriteriaBuilder:

Person.java :

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


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

道。 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;
}
}

使用范例:

public static void main(String[] args)  {
Person person = Dao.getPersonByName("John");
System.out.println(person.getAge());  //John's age
}

不,你不需要使标准查询它将是样板代码你只需要做简单的事情,如果你在 Spring-boot: 在你的回购声明一个具有 findBy [精确字段名称]的方法名称。 例子- 如果您的模型或文档包含一个字符串字段 我的领域,并且您希望通过它来查找,那么您的方法名称将是:

FindBymyField (String myField) ;

我解决了一个类似的问题,我希望通过 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;
}
}

这是一个非常基本的问题:

实体: 学生

@Entity
@Data
@NoArgsConstructor
public class Student{
@Id
@GeneratedValue(generator = "uuid2", strategy = GenerationType.IDENTITY)
@GenericGenerator(name = "uuid2", strategy = "uuid2")
private String id;


@Column(nullable = false)
@Version
@JsonIgnore
private Integer version;


private String studentId;


private String studentName;


private OffsetDateTime enrollDate;


}

存储库接口: 学生资料库

@Repository
public interface StudentRepository extends JpaRepository<Student, String> {


List<Student> findByStudentName(String studentName);


List<Student> findByStudentNameOrderByEnrollDateDesc(String studentName);


@Transactional
@Modifying
void deleteByStudentName(String studentName);
}

注: FindByColumnName: 按条件提供结果

列表 findByStudentName (StringstuentName) 内部转换为查询: < strong > select * from Student where name = ‘ stuentName’

@ Transactional @ 修改 如果要从数据库中删除持久化数据,则。