2014-10-24 4 views
5

Я использую JPA 2.1, Hibernate 4.3.6.Final и MySQL 5.5.37. Я пытаюсь использовать API CriteriaBuilder для написания запроса обновления, который будет обновлять несколько строк. Тем не менее, я получаю «java.lang.IllegalArgumentException: UPDATE/DELETE критерии запросы не могут определить соединения», когда я пытаюсь запустить ниже ...Как написать запрос критериев обновления JPA 2.1 без объединений?

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) 

Edit: Я попытался подзапроса идею, используя этот код ...

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 
+0

Я знаю, что вас не интересуют запросы JPQL для обновления, но это сработает? –

+0

Хорошо, если есть способ JPQL, который может обновить все в одном утверждении, тогда обязательно, я возьму это. Мы используем CriteriaBuilder везде в нашей базе кода, но я читал, что JPQL может делать то же, что и CriteriaBuilder. – Dave

+0

Я бы подумал, что дело доходит до SQL, который ожидает RDBMS (поскольку JPQL обеспечивает то же, что и критерии). Возможно, RDBMS просто не разрешает объединения в инструкции UPDATE/DELETE? (некоторые нет) –

ответ

0

В то время как Я рассмотрел ваш вопрос.

Вы можете использовать HibernateCriteria интерфейс для создания критериев вместо использования 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

Привет, Ваше решение выбирает данные, но ничего не обновляет. Я хочу написать один запрос CriteriaBuilder, который обновляет несколько строк на основе определенных условий. – Dave

0

Если вы просто искать решения, я рекомендую:

  1. Написать выберите данные, которые нужно лет (с CriteriaQuery, потому что вы хотите их).
  2. Просмотрите список извлеченных данных и обновите все в транзакции.

Решение немного сложнее кодировать, но более читаемо, особенно если у вас сложные 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 соответствует (в качестве ассоциированных критериев запроса), но некоторые базы данных могут не поддерживать его (например, MySql не разрешено обновлять the same table as the one specified in the subquery). Если это ваш случай, ваш единственный вариант - сделать, как в моей первой рекомендации.

+0

Спасибо, но я был под впечатлением от JPA 2.1, я мог написать инструкцию UPDATE CriteriaBuilder, которая позволила бы мне обновить несколько строк за один раз. Если на этом нет абсолютно никакого способа сделать это, я вернусь и приму свой ответ. – Dave

+0

Вы можете, конечно, обновить критерии, чтобы обновить больше строк в одном запросе, но PROBABLY не использует JOINs, что кажется вашей проблемой. –

+0

Я открыт для решения, которое обновляет несколько строк за один раз и не использует JOIN. – Dave

0

Соединения возможны только в SELECT в стандартном SQL. Неудивительно, что он не работает в JPQL/Criteria.

Вы должны попытаться использовать Subquery, чтобы внести свою заявку и указать выражение in в вашем предложении where.

Чтобы создать Подзапрос, он будет таким же, как и для стандартного CriteriaQuery, поскольку метод subquery определен в CommonAbstractCriteria - это общий классификатор.

Пример подзапроса доступен в this exchange.

+0

Решение вашего подзапроса почти правильно, вы не должны указывать целую сущность как предикат, а только атрибут Id. Это означает не «mainRoot.in (подзапрос)», а «mainIdAttrPredicate.in (подзапрос)». Другой момент заключается в выборе только Id в подзапросе. Это означает не «. Select (root)», но «. Select (idAttrPredicate)». – bdulac

+0

Если я попытаюсь выбрать только идентификатор в sbuquery --subquery.select (root.get (Classroom_.id)) - код даже не компилируется. Ошибка компиляции: «Выбор метода (выражение) неприменим к аргументам (Path)». – Dave

+0

Путь специализируется на выражении, то есть об общем типе подзапроса . Вы не должны выбирать класс Entity, но тип Id. В обмене Stackoverflow я дал вам ссылку, они используют Integer («Подзапрос sq = c.subquery (Integer.class);»). – bdulac

 Смежные вопросы

  • Нет связанных вопросов^_^