JPQL и список кортежей в качестве параметра для операторов SELECT IN - PullRequest
0 голосов
/ 23 марта 2019

С учетом следующего макета таблицы:

CREATE TABLE things (
    id      BIGINT PRIMARY KEY NOT NULL,
    foo     BIGINT NOT NULL,
    bar     BIGINT NOT NULL
);

Класс сущности (Kotlin):

@Entity
@Table(name = "things")
class Thing(
        val foo: Long,
        val bar: Long
) : AbstractPersistable<Long>()

И хранилище:

interface ThingRepository : JpaRepository<Thing, Long> {
@Query("SELECT t FROM Thing t WHERE t.foo IN ?1")
fun selectByFoos(foos: Iterable<Long>): Iterable<Thing>

@Query("SELECT t FROM Thing t WHERE (t.foo, t.bar) IN ((1, 2), (3, 4))")
fun selectByFoosAndBarsFixed(): Iterable<Thing>

@Query("SELECT t FROM Thing t WHERE (t.foo, t.bar) IN ?1")
fun selectByFoosAndBars(foosAndBars: Iterable<Pair<Long, Long>>): Iterable<Thing>

Следующие двавызовы работают нормально:

repo.selectByFoos(listOf(1L, 3L))
repo.selectByFoosAndBarsFixed()

Однако этот не делает:

repo.selectByFoosAndBars(listOf(Pair(1L, 2L), Pair(3L, 4L)))

Он бросает:

org.springframework.dao.DataIntegrityViolationException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not extract ResultSet

Caused by: org.h2.jdbc.JdbcSQLException: Data conversion error converting "aced00057372000b6b6f746c696e2e50616972fa1b06813de78f780200024c000566697273747400124c6a6176612f6c616e672f4f626a6563743b4c00067365636f6e6471007e000178707372000e6a6176612e6c616e672e4c6f6e673b8be490cc8f23df0200014a000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000000000000017371007e00030000000000000002"; SQL statement:
/* SELECT t FROM Thing t WHERE (t.foo, t.bar) IN ?1 */ select thing0_.id as id1_0_, thing0_.bar as bar2_0_, thing0_.foo as foo3_0_ from things thing0_ where (thing0_.foo , thing0_.bar) in (? , ?) [22018-197]

Caused by: java.lang.NumberFormatException: For input string: "aced00057372000b6b6f746c696e2e50616972fa1b06813de78f780200024c000566697273747400124c6a6176612f6c616e672f4f626a6563743b4c00067365636f6e6471007e000178707372000e6a6176612e6c616e672e4c6f6e673b8be490cc8f23df0200014a000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000000000000017371007e00030000000000000002"

Я думаю, элементы списка передаются какпараметр не вставлен правильно в запрос.Как я могу это исправить?

Конечно, я мог бы вручную построить запрос, например так:

@Repository
class SecondThingRepository(private val entityManager: EntityManager) {
    fun selectByFoosAndBars(foosAndBars: Iterable<Pair<Long, Long>>): Iterable<Thing> {
        val pairsRepr = foosAndBars.joinToString(prefix = "(", postfix = ")") { "(${it.first}, '${it.second}')" }
        val query: TypedQuery<Thing> = entityManager.createQuery("SELECT t FROM Thing t WHERE (t.foo, t.bar) IN $pairsRepr", Thing::class.java)
        return query.resultList
    }
}

Но это замечание кажется очень хорошим.

1 Ответ

1 голос
/ 30 марта 2019

Прежде всего, предостережение: не все базы данных поддерживают синтаксис (t.foo, t.bar) IN ((1, 2), (3, 4)). Использование делает ваше приложение непереносимым.

Я предполагаю, что число Pair s в List может быть произвольным (если нет, есть гораздо более простое решение, включающее изменение выражения IN, например, IN (?1, ?2, ?3) и обновление метода запроса принять три параметра типа List. Я полагаю, это не то, что вы просите, хотя).

Проблема в том, что Hibernate не знает, как сопоставить класс Pair с типом базы данных. Также кажется, что логика разрешения типов элементов коллекции отличается от логики разрешения для внешнего типа, поэтому listOf(listOf(1L, 2L), listOf(3L, 4L)) также не будет работать.

Решение (и это, похоже, хакерство) состоит в том, чтобы представить UserType Hibernate, способный отображать Pair объекты И использовать этот вновь созданный PairType для элементов List.

Прежде всего, добавьте следующий класс в ваш проект:

/* It is absolutely crucial that this class extend Pair. If the Pair class you're using
happens to be final, you will have to implement a Pair class yourself. 
For an explanation of why this is required, have a look at SessionFactory.resolveParameterBindType()
and TypeResolver.heuristicType() methods */
public class PairType extends Pair<Long, Long> implements UserType { 

    public PairType(Long first, Long second) {
        super(first, second);
    }

    public PairType() {
        super(null, null);
    }

    @Override
    public int[] sqlTypes() {
        return new int[] {Types.ARRAY};
    }

    @Override
    public Class returnedClass() {
        return Pair.class;
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
            throws HibernateException, SQLException {
        if (Objects.isNull(value)) {
            st.setNull(index, Types.ARRAY);
        } else {
            final Pair pair = (Pair) value;
            st.setArray(index, new Array() {


                @Override
                public Object getArray() throws SQLException {
                    // TODO Auto-generated method stub
                    return new Object[] {pair.getFirst(), pair.getSecond()};
                }

                ...
                //you can leave the rest of the autogenerated method stubs as they are

            });
        }
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        if (Objects.isNull(value)) {
            return null;
        }
        return Pair.of(((Pair) value).getFirst(), ((Pair) value).getSecond());
    }

    @Override
    public boolean isMutable() {
        return false;
    }

    ...
    //you can leave the rest of the autogenerated method stubs as they are here as well

} 

Затем измените сигнатуру вашего метода на:

selectByFoosAndBars(foosAndBars: Iterable<PairType>): Iterable<Thing>

Примечание: вышеупомянутое решение работало для меня из коробки для базы данных H2. Ваш пробег может варьироваться.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...