如何在没有连接的情况下编写JPA 2.1更新条件查询?

时间:2014-10-24 15:46:43

标签: hibernate jpa join updates jpa-2.1

我正在使用JPA 2.1,Hibernate 4.3.6.Final和MySQL 5.5.37。我正在尝试使用CriteriaBuilder API编写将更新多行的更新查询。但是,当我尝试运行以下内容时,我收到“java.lang.IllegalArgumentException:UPDATE / DELETE条件查询无法定义连接”...

final CriteriaBuilder qb = m_entityManager.getCriteriaBuilder();
    final CriteriaUpdate<MyClassroom> q = qb.createCriteriaUpdate(MyClassroom.class);
    final Root<MyClassroom> root = q.from(MyClassroom.class);
    final Join<MyClassroom, MyClassroomUser> rosterJoin = root.join(MyClassroom_.roster);
    final Join<MyClassroomUser, User> userJoin = rosterJoin.join(MyClassroomUser_.user);

    final Calendar today = Calendar.getInstance();
    q.set(root.get(MyClassroom_.enabled), false)
     .where(qb.and(qb.equal(root.get(MyClassroom_.enabled),true),
                            qb.lessThanOrEqualTo(root.get(MyClassroom_.session).get(MyClassroomSession_.schedule).<Calendar>get(MyClassroomSchedule_.endDate), today)),
                            qb.equal(rosterJoin.get(MyClassroomUser_.classroomRole).get(ClassroomRole_.name), ClassroomRoles.TEACHER),
                            qb.equal(userJoin.get(User_.organization).get(Organization_.importDataFromSis), false));

    return m_entityManager.createQuery(q).executeUpdate();

不使用JPQL,是否有另一种方法可以编写查询,以便我可以利用JPA 2.1更新多行?下面是异常的完整堆栈跟踪...

java.lang.IllegalArgumentException: UPDATE/DELETE criteria queries cannot define joins
    at org.hibernate.jpa.criteria.path.RootImpl.illegalJoin(RootImpl.java:81)
    at org.hibernate.jpa.criteria.path.AbstractFromImpl.join(AbstractFromImpl.java:330)
    at org.hibernate.jpa.criteria.path.AbstractFromImpl.join(AbstractFromImpl.java:324)
    at org.mainco.subco.classroom.repo.MyClassroomDaoImpl.disabledNonCleverExpiredClasses(MyClassroomDaoImpl.java:495)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:319)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at com.sun.proxy.$Proxy66.disabledNonCleverExpiredClasses(Unknown Source)
    at com.follett.fdr.lycea.lms.classroom.test.da.MyClassroomDaoTest.testDisableNonCleverExpiredClass(MyClassroomDaoTest.java:355)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

编辑:我使用此代码尝试了子查询的想法......

   final CriteriaBuilder qb = m_entityManager.getCriteriaBuilder();
    final CriteriaUpdate<Classroom> q = qb.createCriteriaUpdate(Classroom.class);
    final Root<Classroom> mainRoot = q.from(Classroom.class);

    // Subquery to select the classes we want to disable.
    final Subquery<Classroom> subquery = q.subquery(Classroom.class);
    final Root<Classroom> root = subquery.from(Classroom.class);
    final Join<Classroom, ClassroomUser> rosterJoin = root.join(Classroom_.roster);
    final Join<ClassroomUser, User> userJoin = rosterJoin.join(ClassroomUser_.user);
    final SetJoin<User, Organization> orgJoin = userJoin.join(User_.organizations);
    final Calendar today = Calendar.getInstance();
    subquery.select(root)
            .where(qb.and(qb.equal(root.get(Classroom_.enabled),true),
                          qb.lessThanOrEqualTo(root.get(Classroom_.session).get(ClassroomSession_.schedule).<Calendar>get(ClassroomSchedule_.endDate), today)),
                          qb.equal(rosterJoin.get(ClassroomUser_.classroomRole).get(ClassroomRole_.name), ClassroomRoles.TEACHER),
                          qb.equal(orgJoin.get(Organization_.importDataFromSis), false)
                          );

    // Build the update query
    q.set(mainRoot.get(Classroom_.enabled), false)
     .where(mainRoot.in(subquery));

    // execute the update query
    return m_entityManager.createQuery(q).executeUpdate();

但得到例外......

javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not execute statement
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:1771)
    at org.hibernate.jpa.spi.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:87)
    at …
Caused by: java.sql.SQLException: You can't specify target table ‘my_classroom' for update in FROM clause
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1074)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4096)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4028)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2490)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2651)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2734)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2155)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2458)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2375)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2359)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:208)
    ... 49 more

4 个答案:

答案 0 :(得分:0)

在回答你的问题时,我找到了一些解决方法。

您可以使用Hibernate Criteria界面来创建条件,而不是使用JPQL条件,并根据您的实际条件为其添加一些Restriction,如下所示。

Session session = (Session) entityManager.getDelegate();

Criteria criteria = session.createCriteria(MyClassroom.class,"myclassroom");
criteria.createCriteria("myclassroom.roster","rosterJoin");
criteria.createCriteria("rosterJoin.user","userJoin");
criteria.createCriteria("myclassroom.session","session");
criteria.createCriteria("session.schedule","schedule");
criteria.createCriteria("rosterJoin.classroomRole","classroomRole");
criteria.createCriteria("userJoin.organization","organization");


final Calendar today = Calendar.getInstance();

criteria.add(Restrictions.eq("myclassroom.enabled",true));
criteria.add(Restrictions.le("schedule.endDate",today));
criteria.add(Restrictions.eq("classroomRole.name",ClassroomRoles.TEACHER));
criteria.add(Restrictions.eq("organization.importDataFromSis",false));

List<MyClassroom> myClassList = (List<MyClassroom>)criteria.list();

for(MyClassroom room : myClassList) {
    room.setEnabled(false);
    session.saveOrUpdate(room);
}

Hibernate中创建条件实际上会在两个表之间创建INNER JOIN

myClassList将包含您要更新的所有对象,现在迭代它并更新您想要的值。希望这能解决你的问题。

注意:这只是解决问题的一种方法。如果您遇到任何例外,请自行调试。

答案 1 :(得分:0)

如果您只是寻找解决方案,我建议:

  1. 编写您需要的数据选择(使用CriteriaQuery,因为您需要它们)。
  2. 浏览已获取数据的列表并更新事务中的所有内容。
  3. 解决方案的编码有点复杂,但如果你有复杂的JOIN,则更具可读性。

    与上述解决方案相比,一个小改进是在条件构建器的WHERE部分中使用子查询。这应该可以工作,虽然我从来没有尝试过(我总是获取更改数据的粉丝,最终我想要记录一些关于它的东西,比如历史事物)。 JPQL的一个例子:

        Query updateQuery = em.createQuery("UPDATE MyClasroom SET enabled=false WHERE enabled=true AND id IN (SELECT id FROM MyClasroom WHERE ...)");
        updateQuery.setParameter("phoneNo", "phoneNo");
        ArrayList<Long> paramList = new ArrayList<Long>();
        paramList.add(123L);
        updateQuery.setParameter("ids", paramList);
        int nrUpdated = updateQuery.executeUpdate();
    

    这符合JPA(与其关联的Criteria查询),但某些数据库可能不支持它(例如,不允许MySql更新the same table as the one specified in the subquery)。如果这是你的情况,你唯一的选择是在我的第一个推荐中做。

答案 2 :(得分:0)

只能在标准SQL中的SELECT中使用联接。毫不奇怪它在JPQL / Criteria中不起作用。

您应该尝试使用Subquery进行加入,并在in子句中指定where表达式。

要创建子查询,它与标准CriteriaQuery相同,因为在CommonAbstractCriteria中定义subquery方法是一般分类器。

this exchange中提供了子查询的示例。

答案 3 :(得分:0)

  1. JPA中的更新不允许加入。
  2. 即使使用

    之类的子查询

    update MyClassroom set enabled = false其中id在 (从MyClassroom c中选择c.id,然后加入c.roster r ...其中...)

将失败,因为MySQL不允许这样做-更新并从同一表(doc)中选择。

  1. 在JPQL中也不可能(由于第2点)。