Как правильно установить Map как JavaType в в MyBatis?

#java #mybatis #lombok

#java #mybatis #ломбок

Вопрос:

Описание проблемы:

У меня есть java.lang.NoSuchMethodException: bar.Foo.<init>(java.lang.Long, java.lang.String, java.util.Map) , когда я пытаюсь получить неизменяемые объекты Foo из базы данных с помощью MyBatis и не могу понять, что не так: (

У меня есть неудачный случай, когда я аннотирую объект результата с помощью @Value, а успешный случай аннотируется с помощью @Data (они приведены ниже в соответствующих заголовках). Итак, я хотел бы использовать @Value, но не понимаю, что я делаю неправильно..

Неудачный случай:

Итак, у меня есть результирующий класс, который является неизменяемым:

 @Value
public class Foo {
    long entryId;
    String description;
    Map<String, String> params;
}
 

Он хранится в postgres в таблице foos, params значение поля сохраняется в формате JSON в соответствующем столбце той же таблицы (тип — VARCHAR, записи "{key=value}" в случае params = Map.of("key", "value") .
Я хотел бы создать экземпляр моего объекта этого класса из базы данных, поэтому я использую следующий код MyBatis:

 <resultMap id="foo" type="bar.Foo">
     <constructor>
         <arg column="entry_id" javaType="java.lang.Long"/>
         <arg column="description" javaType="java.lang.String"/>
         <arg column="params" javaType="java.util.Map" jdbcType="VARCHAR" typeHandler="bar.MapToJsonStringHandler"/>
     </constructor>
</resultMap>
 

Вот мой обработчик типов (он уже был успешно использован в другом случае с объектом @Data вместо @Value, так что я думаю, что это правильно само по себе):

 public class MapToJsonStringHandler implements TypeHandler<Map<String, String>> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @SneakyThrows(IOException.class)
    @Override
    public void setParameter(PreparedStatement ps, int i, Map<String, String> parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
        ps.setNull(i, Types.VARCHAR);
    } else {
        ps.setString(i, objectMapper.writeValueAsString(parameter));
    }
}

@Override
public Map<String, String> getResult(ResultSet rs, String columnName) throws SQLException {
    String result = rs.getString(columnName);
    return convertToMap(result);
}


@Override
public Map<String, String> getResult(ResultSet rs, int columnIndex) throws SQLException {
    String result = rs.getString(columnIndex);
    return convertToMap(result);
}

@Override
public Map<String, String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
    String result = cs.getString(columnIndex);
    return convertToMap(result);
}

@SneakyThrows(IOException.class)
private Map<String, String> convertToMap(String result) {
    return result == null ? Collections.emptyMap() : objectMapper.readValue(result, Map.class);
}
 

}

Вот SQL:

 <select id="getFoos" resultMap="foo">
     SELECT *
     FROM foos
     <![CDATA[WHERE needed_tm < now()]]>
</select>
 

И когда я получаю свои Foos, у меня есть java.lang.NoSuchMethodException: bar.Foo.<init>(java.lang.Long, java.lang.String, java.util.Map) .

Забавно, что это работает как шарм, когда я использую @Data вместо @Value:

Успешный случай:

 @Data
@NoArgsConstructor
@AllArgsConstructor
public class Foo {
    long entryId;
    String description;
    Map<String, String> params;
}

<resultMap id="foo" type="bar.Foo">
     <result column="entry_id" property="entryId"/>
     <result column="description" property="description"/>
     <result column="params" property="params" typeHandler="bar.MapToJsonStringHandler"/>
</resultMap>
 

SQL и обработчик типов те же, что и в неудачном случае.

Не могли бы вы быть так добры, чтобы дать мне подсказку, чего я, возможно, не понимаю? Заранее спасибо!

Обновление 1:

 @Value
@AllArgsConstructor
public class Foo {
     long entryId;
     String description;
     Map<String, String> params;
}
 

дает такое же исключение. В любом случае, как я понимаю, @Value уже имеет @AllArgsConstructor по дизайну (https://projectlombok.org/features/Value )

Комментарии:

1. Я буду снимать здесь в темноте, так как я думаю, что Java autoboxing должен разобраться в этом, но измените long на Long и используйте значение так же, как вы это сделали

2. Привет, Ник. Отметка ответа пометит вопрос как «решенный». Нет необходимости редактировать его в вашем названии или вопросе. Я вернулся к предыдущей версии вашего вопроса, чтобы удалить упоминания о «решаемой».

3. Привет, ТТ, спасибо, хорошо 🙂

Ответ №1:

java.lang.Исключение NoSuchMethodException: bar.Foo.(java.lang.Длинный, java.lang.Строка, java.util.Map) <— Означает, что конструктор не определен.

Когда вы используете @AllArgsConstructor, вы говорите lombok создать конструктор со всеми аргументами (Long, String, Map), вот почему это работает для вас.

Согласно documentation @Data:

Теперь @Data Все вместе: ярлык для @toString, @EqualsAndHashCode, @Getter для всех полей, @Setter для всех полей, не являющихся конечными, и @RequiredArgsConstructor

https://projectlombok.org/features/Data

Вот почему в этот момент он находит конструктор и не выдает ошибку (noSuchMethod ) при использовании @Data .


 <resultMap id="foo" type="bar.Foo">
     <constructor>
         <arg column="entry_id" javaType="java.lang.Long"/>
         <arg column="description" javaType="java.lang.String"/>
         <arg column="params" javaType="java.util.Map" jdbcType="VARCHAR" typeHandler="bar.MapToJsonStringHandler"/>
     </constructor>
</resultMap>
 

Поскольку вы указываете тип данных как java.lang.Длинный, он ожидает, что конструктор будет определен с этим типом параметра.

Я считаю, что с @Data вам помог не конструктор, а установщики (боксы) long To Long .

Попробуйте использовать @Value и измените long на Long в своем классе. Посмотрите, работает ли это.

Lombok сгенерирует конструктор с (long, String, Map), а не (Long, String, Map)

Любопытно узнать, какую версию Java вы используете.

Комментарии:

1. Спасибо! После того, как я изменил поля класса на Long, это наконец-то работает 🙂

2. Из любопытства, какую версию java вы используете 🙂 @Ник

3. Java 11, jdk 11.0.9