Отображение входных и выходных параметров с MyBatis

Я учусь использовать MyBatis. Честно говоря, мне очень нравятся эти рамки. Он прост в использовании, и я доволен им, потому что я могу использовать с ним свои команды sql:) Я использую базу данных MyBatis 3.4.2 и PostgreSQL.

Например, мне нравится, как легко выполнить запрос перед вставкой с @SelectKey аннотаций. И отображение данных работает как обаяние, если я добавляю некоторую аннотацию перед методом интерфейса, что-то вроде этого: @Results({ @Result(property = "javaField", column = "database_field", javaType = TypeHandler.class),

Что мне не нравится (и я надеюсь, что вы можете направить меня в правильном направлении):


(Проблема 1) У меня есть запросы, которые позволяют мне использовать нулевое и нормальное значение без каких-либо дополнительных java-операторов "если", чтобы проверить, содержит ли переменная нулевое или нулевое значение. Они выглядят так:

SELECT * FROM table
WHERE key_name = ? AND ((? IS NULL AND user_id IS NULL) OR User_id = ?) 

С JDBC мне нужно следующее:

stmt = connection.prepareStatement(query);
stmt.setString(1, "key");
stmt.setString(2, userId);
stmt.setString(3, userId);

Как видите, мне нужно дважды передать userId, потому что именно так работает JDBC. Честно говоря, я ожидал, что следующий код будет работать с MyBatis, но, к сожалению, он не работает. Третий параметр еще нужно определить.

Интересно, возможно ли добавить эту функцию в MyBatis? Это должно быть хорошо, если MyBatis может связывать userId дважды автоматически, что-то вроде этого:

@Select("SELECT * FROM table key_name = #{key} and ((#{userId} is null and user_id is null) OR user_id = #{userId})
SomeClass findByKeyAndUserId(String key, Long userId);

То, что я действительно сделал, это следующее. Я ненавижу это, потому что это сложно и дополнительные java "если" заявление необходимо:

@Select("SELECT * FROM table WHERE key_name = #{key} AND COALESCE(user_id, -1) = #{userId}")
SomeClass findByKeyAndUserId(String key, Long userId);

userId = (userId == null) ? -1 : userId;
SomeClass abc = mapper.findByKeyAndUserId(key, userId);

Я не знаю, как лучше управлять этой ситуацией с MyBatis. Пожалуйста, ведите меня.


(Проблема 2) Отображение в случае @Select, Есть ли способ избежать повторения кода при отображении результатов запросов с одинаковым типом результата?

1-й запрос:

@Select("SELECT * FROM table WHERE ...")
@Results({
        @Result(property = "key", column = "key_name", javaType = String.class),
        @Result(property = "value", column = "key_value", javaType = String.class),
        @Result(property = "userId", column = "user_id", javaType = Long.class),
        @Result(property = "interval", column = "interval", javaType = Long.class),
        @Result(property = "description", column = "description", javaType = String.class),
        @Result(property = "status", column = "status", typeHandler = StatusTypeHandler.class)
})
SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

2-й запрос:

@Select("SELECT * FROM table WHERE <different conditions then before>")
@Results({
        <I need to add here the exact same code then before in query 1>
})
SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

Могу ли я как-то использовать код, связанный с отображением? Мне нужно добавить отображение, потому что я использую специальный тип обработчика для поля состояния. Я использую аннотации на основе конфигурации.


(Выпуск 3) @Param аннотации я ничего не видел в документации о @Param аннотаций. Трудно было понять, почему мои параметры Java не были правильно ограничены. Наконец я понял, что @Param аннотация отсутствовала в моем коде. Почему это не упоминается в официальной документации? Я сделал что-то не так и @Param не нужно использовать?

Этот код работает нормально:

SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

Это не работает:

SomeClass findByKeyAndUserId(String key, Long userId);

ОБНОВЛЕНИЕ ДЛЯ (Выпуск 1)

Моя первая идея была похожа на упомянутое @blackwizard: "Mybatis связывает параметры по имени, затем один раз, два раза, N раз, как это может случиться, если время на него ссылается".

Но это на самом деле не работает должным образом. Это работает, если userId не нулевой. Если это ноль, я получаю хорошее исключение, которое возвращается из базы данных. Я предполагаю, что MyBatis неправильно связывает нулевое значение. Может быть, это ошибка. Я не знаю:(

Исключение:

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.postgresql.util.PSQLException: ERROR: could not determine data type of parameter $2
### The error may exist in com/.../dao/TableDao.java (best guess)
### The error may involve ....dao.Table.findByKeyAndUserId-Inline
### The error occurred while setting parameters
### SQL: SELECT * FROM table WHERE key_name = ? AND (? IS NULL AND user_id IS NULL) OR user_id = ? AND status = 1
### Cause: org.postgresql.util.PSQLException: ERROR: could not determine data type of parameter $2

Наконец я нашел три разных решения.

(Проблема 1: решение 1)

Я следовал тому, что упомянул @blackwizard, и смог переместить условие userId = (userId == null) ? -1 : userId от Java до уровня MyBatis. И это не плохо! Правильный синтаксис: <if test='userId==null'><bind name='userId' value='-1'/></if>,

(Проблема 1: решение 2) Причина, по которой я возвращаюсь could not determine data type of parameter $2 ошибка от postgres связана с тем, что в случае нулевого значения драйвер JDBC не может определить тип параметра. Итак, давайте определим это вручную.

@Select("SELECT * FROM table "
        + "WHERE key_name = #{key} AND ((#{userId}::BIGINT IS NULL AND user_id IS NULL) OR user_id = #{userId})")
})
SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

(Проблема 1: решение 3) 2-е решение зависит от PorstgreSQL. Следующее решение полностью не зависит от базы данных. Спасибо за @blackwizard за хороший комментарий.

@Select("SELECT * FROM table "
        + "WHERE key_name = #{key} AND ((#{userId, jdbcType=BIGINT} IS NULL AND user_id IS NULL) OR user_id = #{userId})")
})
SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

Лично я предпочитаю решение 3. Он содержит меньше дополнительного кода.

2 ответа

Решение

Выпуск 1:

Назовите параметры:

@Select("SELECT * FROM table key_name = #{key} and ((#{userId} is null and user_id is null) OR user_id = #{userId}")
SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

Mybatis связывает параметры по имени, затем один раз, два раза, N раз, как это может случиться, если время на него ссылаются.

Вы можете сделать if в Mybatis: с XML-тэгами, хотя это не очень хорошо... в любом случае, это хороший трюк, который вы можете использовать когда-нибудь для другой цели. Чтобы использовать теги XML в значении аннотации, значение должно быть встроено в <script> помечать точно в начале и конце строки.

@Select({"<script>",
         "<if 'userId==null'><bind name='userId' value='1'/></if>",
         "SELECT * FROM table WHERE key_name = #{key} ", 
         "AND COALESCE(user_id, -1) = #{userId}", 
         "</script>"})
SomeClass findByKeyAndUserId(@Param("key") String key, @Param("userId") Long userId);

Вы также можете использовать typeHandler, чтобы установить значение по умолчанию, просто используйте его с параметром: #{userId, typeHandler=CustomDefaultValueTypeHandler}

Редактировать: ответить на дополнительный вопрос: Если вы хотите разрешить передачу пустых значений вместо обработки замены по умолчанию, тогда вы должны дать Mybatis некоторую подсказку о предполагаемом типе связанного параметра, поскольку он не может разрешить фактический тип нулевого значения, так как он не знает / см. объявление переменной в интерфейсе Mapper. Так: #{userId, javaType=int,jdbcType=NUMERIC}, Только один из обоих атрибутов может быть достаточным.

Документация гласит:

Как и остальная часть MyBatis, javaType почти всегда может быть определен из объекта параметра, если этот объект не является HashMap. Затем следует указать javaType, чтобы убедиться, что используется правильный TypeHandler.

ПРИМЕЧАНИЕ Тип JDBC требуется JDBC для всех столбцов, допускающих значение NULL, если в качестве значения передано значение NULL. Вы можете исследовать это самостоятельно, прочитав JavaDocs для метода PreparedStatement.setNull().

Проблема 2: вы определенно не можете повторно использовать / объединять то, что вы определяете в аннотации. Это не из-за Mybatis, а из-за аннотаций. Вам нужно будет определить карты результатов в XML, ссылаясь на них с помощью @ResultMap. Документация гласит:

ResultMap   Method  N/A     This annotation is used to provide the id of a <resultMap> element in an XML mapper to a @Select or @SelectProvider annotation. This allows annotated selects to reuse resultmaps that are defined in XML. This annotation will override any @Results or @ConstructorArgs annotation if both are specified on an annotated select.

Проблема 3: как я уже ответил на ваш другой вопрос, аннотации @Param позволяют преобразовать удобный список параметров в карту, такую ​​как:

Map<String, Object> params = new HashMap<String, Object>();
params.put("key", key);
params.put("userId", userId);

Я согласен, что документация Mybatis может быть лучше, и вы найдете больше ресурсов, таких как здесь.

Однако в документации Mybatis говорится о аннотации @Param

@Param  Parameter   N/A     If your mapper method takes multiple parameters, this annotation can be applied to a mapper method parameter to give each of them a name. Otherwise, multiple parameters will be named by their position prefixed with "param" (not including any RowBounds parameters). For example #{param1}, #{param2} etc. is the default. With @Param("person"), the parameter would be named
#{person}.

Выпуск 3:

Пожалуйста, попробуйте параметр компиляции -parameters, предоставляемый начиная с JDK 8. Вы можете опустить аннотацию @Param.

См. https://github.com/mybatis/mybatis-3/issues/549

Благодарю.

Другие вопросы по тегам