[英]What is best way to get rid of LazyInitializationException on calculated fields?
[英]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();
.
由于User
对Activity
的引用是LAZY
,JPA 只会在您尝试通过代码访问它时加载它,就像任何以任何方式访问变量User.activities
一样。 这使得 JPA 加载引用的Activity
。
我认为那里发生了什么:
getUserByUserName()
方法中创建(从数据库中读取)那些User
时,JPA 会保留(已创建的User
保留)对创建它们时使用的EntityManager entityManager
的引用。User.activities
,JPA 将尝试使用EntityManager entityManager
加载这些活动。EntityManager entityManager
(就像您在final
一条语句中所做的那样),那么加载将失败并出现您随后得到的LazyInitializationException
。解决方案:
由于我没有唱 Hibernate,也没有检查它的代码库,所以我不知道entityManagerFactory.createEntityManager();
如果已经存在,实际上正在创建单独的实例,以及如何管理所有EntityManager
实例。
但可能最好永远不要关闭任何EntityManager
; 相反,让 JPA 实现 (Hibernate) 来处理这个问题。 有了将依赖项注入类的所有可能性,我敢打赌 Hibernate 有一些非常好的机制来处理这种资源管理。
作为替代方案,让 Hiberante 注入EntityManager
,这样您甚至不必一开始就创建它。
最后,您仍然可以坚持使用 LAZY 加载,如果它可以提高您的初始和长期性能。 只是不要关闭那些EntityManager
。
如果您需要对此事有更深入的了解,
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(在本例中为“用户”类):
这是处理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);
}
}
这就是我实现和使用它的方式。
我相信 Spring 使用了一个非常相似的系统,并将其称为“...Repository”。 如果你愿意,你也可以检查它并获取它的源代码并进行调整。 通常这样做时,@EJB、@PersistenceContext 和@Inject
在 GlassFish/ @EJB
、 @PersistenceContext
或 Hibernate 之间不能很好地传输,因为并非所有注释都支持所有这些注释。 正如我所说,这是 Payara 特有的,我从未在其他容器中测试过它,但这种方法应该适用于任何地方。
回顾一下:这在很大程度上取决于依赖注入,我让容器完成所有工作。
规则是
实体不应该逃脱交易界限
延迟加载是 Hibernate 的杀手级特性,不用怕。
因此,存储库/DAO 应该返回实体。 该服务应该是事务性的 (*),管理实体但在外部返回 DTO。
考虑为此目的使用任何 java bean 映射器以避免猴子工作。
这允许您在不需要时不加载不必要的属性。
(*) 如果没有长期存在的操作,例如 http 调用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.