繁体   English   中英

处理 LazyInitializationException 的最佳方法是什么

[英]What is the best way to handle LazyInitializationException

我最近一直在为 Hibernate 苦苦挣扎。 我最近遇到了一个问题,希望能得到一些帮助。 我有两个实体:

1.用户

@Entity
public class User{

@ID
private Long id;

@OneToMany (mappedBy = "user")
private Set<Activity> activities;

...
}

2.活动:

@Entity
public class Activity {

@ID
private Long id;

@ManyToOne
private User user;

...
}

所以在这里,因为我没有将用户活动 fetchType 设置为 EAGER,所以当我从数据库中获取用户实体时,所有活动都将被延迟获取。

我在 UserRepository 中所做的是:

@Override
public User getUserByUserName(String userName) {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    Query query = entityManager.createQuery
            ("from User u where u.userName = :user_name", User.class);
    query.setParameter("user_name", userName);
    try{
        return (User) query.getSingleResult();
    } catch(NoResultException e) {
        return null;
    } finally {
        entityManager.close();
    }
}

这样做时,当我想使用获取的用户活动时,我得到了 The LazyInitializationException。 但我所做的是从代码中删除 finaly 块:

@Override
public User getUserByUserName(String userName) {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    Query query = entityManager.createQuery
            ("from User u where u.userName = :user_name", User.class);
    query.setParameter("user_name", userName);
    try{
        return (User) query.getSingleResult();
    } catch(NoResultException e) {
        return null;
    }
 }

这解决了异常。 我想知道这是否是正确的方法,还是我应该将用户活动 fetchType 更改为 EAGER?

我猜你遇到的问题来自于关闭entityManager.close(); .

由于UserActivity的引用是LAZY ,JPA 只会在您尝试通过代码访问它时加载它,就像任何以任何方式访问变量User.activities一样。 这使得 JPA 加载引用的Activity

认为那里发生了什么:

  1. 当您在getUserByUserName()方法中创建(从数据库中读取)那些User时,JPA 会保留(已创建的User保留)对创建它们时使用的EntityManager entityManager的引用。
  2. 因此,如果您稍后尝试访问User.activities ,JPA 将尝试使用EntityManager entityManager加载这些活动。
  3. 如果您已经关闭了那个EntityManager entityManager (就像您在final一条语句中所做的那样),那么加载将失败并出现您随后得到的LazyInitializationException

解决方案:

由于我没有唱 Hibernate,也没有检查它的代码库,所以我不知道entityManagerFactory.createEntityManager(); 如果已经存在,实际上正在创建单独的实例,以及如何管理所有EntityManager实例。

但可能最好永远不要关闭任何EntityManager 相反,让 JPA 实现 (Hibernate) 来处理这个问题。 有了将依赖项注入类的所有可能性,我敢打赌 Hibernate 有一些非常好的机制来处理这种资源管理。

作为替代方案,让 Hiberante 注入EntityManager ,这样您甚至不必一开始就创建它。

最后,您仍然可以坚持使用 LAZY 加载,如果它可以提高您的初始和长期性能。 只是不要关闭那些EntityManager

如果您需要对此事有更深入的了解,

  • 检查 Hibernate 的源代码/预处理器/代码注入/字节码编织机制
  • 或者使用一些 JVM memory 分析工具来查看实例化EntityManager的数量是否随着调用getUserByUserName()方法线性增加。

更新:显示一个完全不同的选择

我个人使用Payara,并使用@EJB private UserCRUD mUserCRUD; 注入数据访问对象,我很久以前称之为 CRUD(用于创建检索更新删除)并且仍然坚持使用它们。

基本原理是这3个步骤:

第 1 步:我在库中有一个通用基础 class,注入了@PersistenceContext protected EntityManager mEM;

@Stateless
public abstract class TemplateCRUD_Simple<T> implements TemplateCRUD_Simple_Interface<T> {

    @PersistenceContext protected EntityManager mEM;

    protected final Class<T> mType;

    public TemplateCRUD_Simple(final Class<T> pClass) {
        mType = pClass;
    }



    /*
     * INTERNALS
     */

    @Override public String getTableName() {
        return mType.getSimpleName();
    }
    @Override public String getNativeTableName() {
        final Table table = mType.getAnnotation(Table.class);
        if (table != null) return table.name();
        return getTableName();
    }
    @Override public EntityManager getEntityManager() {
        return mEM;
    }



    /*
     * CREATE
     */

    @Override public T create(final T t) {
        return createAny(t);
    }
    @Override public <U> U createAny(final U u) {
        mEM.persist(u);
        mEM.flush();
        mEM.refresh(u);
        return u;
    }



    /*
     * RETRIEVE
     */

    @Override public T find(final long pID) throws JcXEntityNotFoundException {
        final T ret = find(pID, null);
        if (ret == null) throw new JcXEntityNotFoundException(getTableName() + " with ID " + pID + " cannot be found!");
        return ret;
    }


    @Override public T find(final long pID, final T pDefault) {
        final T ret = mEM.find(mType, Long.valueOf(pID));
        if (ret == null) return pDefault;
        return ret;
    }
    @Override public T find(final Long pID, final T pDefault) {
        if (pID == null) return pDefault;
        return find(pID.longValue(), pDefault);
    }
    @Override public T findCreate(final long pID) throws InstantiationException, IllegalAccessException {
        final T item = find(pID, null);
        if (item != null) return item;

        final T item2 = mType.newInstance();
        return create(item2);
    }

    // a lot more methods here
}

及其接口定义,也在库中:

public interface TemplateCRUD_Simple_Interface<T> {

    EntityManager getEntityManager();
    String getTableName();
    String getNativeTableName();


    // create
    T create(T t);
    <U> U createAny(U t);

    // retrieve
    T find(long pID) throws JcXEntityNotFoundException;
    T find(long pID, T pDefault);
    T find(Long pID, T pDefault);
    T findCreate(long pID) throws InstantiationException, IllegalAccessException;
    List<T> findAll(String pColName, Object pValue, final boolean pCaseSensitive);
    List<T> findAll(String pColName, Object pValue);
    List<T> findAll(String pColName, String pValue, final boolean pCaseSensitive);
    List<T> findAll(String pColName, String pValue);
    List<T> findAll(String pColName, long pValue);
    List<T> findAllByFieldName(String pFieldName, Object pValue, final boolean pCaseSensitive);
    List<T> findAllByFieldName(String pFieldName, Object pValue);
    List<T> findWhereContains(final String pColName, final String pValue, final boolean pCaseSensitive);
    List<T> findWhereContains(final String pColName, final String pValue);
    List<T> getAll();
    List<Long> getAllIds();
    List<T> getByIds(final Collection<Long> pIds);

    // update
    T update(T t);
    void updateProperties(T pItem, Map<String, String[]> pMatches);
    T updateItem(Map<String, String[]> pMatches, long pID) throws InstantiationException, IllegalAccessException;
    ArrayList<T> updateItems(String pEntityParamName, Map<String, String[]> pMap) throws InstantiationException, IllegalAccessException;

    // delete
    T delete(long pId);

    // misc
    long countAll();
    Object getID(T pItem);
    long getIDLong(T pItem);
    boolean contains(T pItem);
    void detach(final T pItem);

    @Deprecated String getFieldNameInDb(final String pFieldName, final String pDefault);



    //  private List<T> getOverdueForXIn(final int pDays, final String pVarName);
    List<T> getOverdueForDeletion(final boolean pAddAlreadyWarned);
    List<T> getOverdueForUpdate(final boolean pAddAlreadyWarned);

}

第 2 步:对于每个自定义 Class,我都有一个 CRUD(在本例中为“用户”类):

  • CrudBase_BaseEntity 基本上是从 TemplateCRUD_Simple 派生的,只是在两者之间多了几个步骤以获得更大的灵活性
  • UserCRUD 扩展了 TemplateCRUD_Simple,因此我可以轻松使用那些通用方法
  • 如果我需要专门的方法,我只需将它们添加到 UserCRUD 的代码中

这是处理User实体的示例:

@Entity
@Table(name = "PT_User")
public class User extends _BaseEntity<User> {...}

这是它的 CRUD/DAO:

@Stateless
public class UserCRUD extends CrudBase_BaseEntity<User> {

    public UserCRUD() {
        super(User.class);
    }

    public long getRegisteredUsersCount() {
        final String sql = "SELECT COUNT(d) FROM " + getTableName() + " d";
        final Query q = mEM.createQuery(sql);
        final Long count = (Long) q.getSingleResult();
        if (count == null) return 0;
        return count.longValue();
    }



    public User findUserByUsernameOrEmail(final String pUid, final String pMail) {
        final TypedQuery<User> query = mEM.createQuery("SELECT i FROM " + getTableName() + " i WHERE lower(i.email)=lower(:userid) OR lower(i.email)=lower(:usermail)", mType);
        query.setParameter("userid", pUid);
        query.setParameter("usermail", pMail);
        final List<User> list = query.getResultList();
        if (list == null || list.size() < 1) return null;
        return list.get(0);
    }



    public List<User> getAllAdmins() {
        final TypedQuery<User> query = mEM.createQuery("SELECT i FROM " + getTableName() + " i WHERE i.isAdmin = TRUE", mType);
        final List<User> list = query.getResultList();
        return list;
    }



    public List<User> getInvalidUsers() {
        final TypedQuery<User> query = mEM.createQuery("SELECT i FROM " + getTableName() + " i "
                + "WHERE (i.username IS NULL"
                + " OR i.fullname IS NULL)"
                + " AND i.email IS NULL", mType);
        final List<User> list = query.getResultList();
        return list;
    }

}

第 3 步:在 servlets 中,我只是通过@EJB注解注入它,然后像这样使用它:

@WebServlet("/dyn/user/getAll")
@WebServletParams({})
@WebServletDescription()
public class GetAll extends BaseServlet {
    private static final long serialVersionUID = -4567235617944396165L;

    @EJB private UserCRUD mCRUD;

    @Override protected void doGet_(final HttpServletRequest pReq, final HttpServletResponse pResp) throws IOException {
        USessionManager.ensureUserLoggedInAndAdmin(pReq);

        final List<User> items = mCRUD.getAll();

        items.sort((final User pO1, final User pO2) -> JcUString.compareTo(pO1.getFullname(), pO2.getFullname(), false));

        JSON.send(items, pResp);
    }

}

这就是我实现和使用它的方式。

  • 起初它有很多开销
  • 但是设置新的类和 CRUD 真的很容易
  • 使用它们是安全的
  • 我在我的 JEE 库中使用它,所以我不必复制代码,然后我制作的任何补丁或添加都可用于所有使用它的项目
  • 如果需要,我总是可以访问(再次:注入的)TemplateCRUD 中的 EntityManager 实例。

我相信 Spring 使用了一个非常相似的系统,并将其称为“...Repository”。 如果你愿意,你也可以检查它并获取它的源代码并进行调整。 通常这样做时,@EJB、@PersistenceContext 和@Inject在 GlassFish/ @EJB@PersistenceContext或 Hibernate 之间不能很好地传输,因为并非所有注释都支持所有这些注释。 正如我所说,这是 Payara 特有的,我从未在其他容器中测试过它,但这种方法应该适用于任何地方。

回顾一下:这在很大程度上取决于依赖注入,我让容器完成所有工作。

规则是

实体不应该逃脱交易界限

延迟加载是 Hibernate 的杀手级特性,不用怕。

因此,存储库/DAO 应该返回实体。 该服务应该是事务性的 (*),管理实体但在外部返回 DTO。

考虑为此目的使用任何 java bean 映射器以避免猴子工作。

这允许您在不需要时不加载不必要的属性。

(*) 如果没有长期存在的操作,例如 http 调用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM