未找到Java Record和BeanPropertyRowMapper的默认构造函数

cwxwcias  于 2022-10-04  发布在  Java
关注(0)|答案(2)|浏览(221)

我正在使用新的Java 14和Spring Boot。我使用了新的Cool Record,而不是用于数据持有者的常规Java类。

  1. public record City(Long id, String name, Integer population) {}

在我的服务类的后面,我使用SpringBeanPropertyRowMapper来获取数据。

  1. @Override
  2. public City findById(Long id) {
  3. String sql = "SELECT * FROM cities WHERE id = ?";
  4. return jtm.queryForObject(sql, new Object[]{id},
  5. new BeanPropertyRowMapper<>(City.class));
  6. }

我最后出现了以下错误:

  1. Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.zetcode.model.City]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.zetcode.model.City.<init>()
  2. at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:145) ~[spring-beans-5.2.3.RELEASE.jar:5.2.3.RELEASE]

如何为记录添加默认构造函数,或者是否有其他方法可以修复此问题?

x33g5p2x

x33g5p2x1#

只需通过为字段提供默认值来显式声明它:

  1. public record City(Long id, String name, Integer population) {
  2. public City() {
  3. this(0L, "", 0)
  4. }
  5. }

一个重要的注解。BeanPropertyRowMapper扫描setters/getters来膨胀您的Record示例,因为Record是不可变的,没有setter,并且与Java Beans规范不兼容,您会得到并空Record。请阅读这篇SO。创建记录的唯一方法是使用构造函数。因此,您有两个选择:要么使用纯Java Bean,要么实现定制的行Map器。

它看起来最简单的方式是:

  1. @Override
  2. public City findById(final Long id) {
  3. final var sql = "SELECT * FROM cities WHERE id = ?";
  4. return jtm.queryForObject(
  5. sql,
  6. new Object[]{ id },
  7. (rs, rowNum) -> new City(
  8. rs.getLong("id"),
  9. rs.getString("name"),
  10. rs.getInt("population")));
  11. }

或者,您可以使用reflection

反射API

以下公共方法将添加到java.lang.Class中:

  1. RecordComponent[] getRecordComponents()
  2. boolean isRecord()

方法getRecordComponents()返回一个由java.lang.refect.RecordComponent对象组成的数组,其中的java.lang.refirect.RecordComponent是一个新类。此数组的元素与记录的组件相对应,顺序与它们在记录声明中出现的顺序相同。可以从数组中的每个RecordComponent中提取其他信息,包括其名称、类型、泛型类型、注解及其访问器方法。

如果给定类被声明为记录,则方法isRecord()返回TRUE。(请比较isEnum()。)

使用这些方法和类#getConstructor(类...参数类型)和构造函数#newInstance(对象...Initargs),您可以动态创建记录。但要记住,反思可能会带来一些开销,并影响你的表现。

我添加了一个使用反射的RecordRowMapper示例和几个tests

  1. package by.slesh.spring.jdbc.core;
  2. import org.springframework.jdbc.IncorrectResultSetColumnCountException;
  3. import org.springframework.jdbc.core.RowMapper;
  4. import org.springframework.jdbc.support.JdbcUtils;
  5. import java.lang.reflect.Constructor;
  6. import java.lang.reflect.InvocationTargetException;
  7. import java.lang.reflect.RecordComponent;
  8. import java.sql.ResultSet;
  9. import java.sql.ResultSetMetaData;
  10. import java.sql.SQLException;
  11. import java.util.*;
  12. public class RecordRowMapper<T> implements RowMapper<T> {
  13. private final Constructor<T> ctor;
  14. private final List<Arg> args;
  15. public RecordRowMapper(final Class<T> model) {
  16. if (!model.isRecord()) {
  17. throw new IllegalArgumentException(
  18. model + " should be a record class");
  19. }
  20. final RecordComponent[] components = model.getRecordComponents();
  21. this.args = new ArrayList<>(components.length);
  22. final Class<?>[] argTypes = new Class[components.length];
  23. for (int i = 0; i < components.length; ++i) {
  24. final RecordComponent c = components[i];
  25. this.args.add(new Arg(i, c.getName(), c.getType()));
  26. argTypes[i] = c.getType();
  27. }
  28. try {
  29. this.ctor = model.getConstructor(argTypes);
  30. } catch (NoSuchMethodException e) {
  31. throw new RuntimeException(
  32. "Couldn resolve constructor for types " + Arrays.toString(argTypes));
  33. }
  34. }
  35. @Override
  36. public T mapRow(final ResultSet resultSet, final int rowNumber) throws SQLException {
  37. final var metaData = resultSet.getMetaData();
  38. final int columnCount = metaData.getColumnCount();
  39. if (columnCount < args.size()) {
  40. throw new IncorrectResultSetColumnCountException(
  41. args.size(), columnCount);
  42. }
  43. try {
  44. return ctor.newInstance(extractCtorParams(
  45. resultSet, createPropertyToColumnIndexMap(
  46. metaData, columnCount)));
  47. } catch (InstantiationException
  48. | IllegalAccessException
  49. | InvocationTargetException e) {
  50. throw new RuntimeException(e);
  51. }
  52. }
  53. private Object[] extractCtorParams(
  54. final ResultSet resultSet,
  55. final Map<String, Integer> propertyToColumnIndexMap)
  56. throws SQLException {
  57. final var params = new Object[args.size()];
  58. for (final var arg : args) {
  59. final int columnIndex = propertyToColumnIndexMap.get(arg.name);
  60. params[arg.order] = JdbcUtils.getResultSetValue(
  61. resultSet, columnIndex, arg.type);
  62. }
  63. return params;
  64. }
  65. private Map<String, Integer> createPropertyToColumnIndexMap(
  66. final ResultSetMetaData metaData,
  67. final int columnCount)
  68. throws SQLException {
  69. final Map<String, Integer> columnPropertyToIndexMap = new HashMap<>(columnCount);
  70. for (int columnIndex = 1; columnIndex <= columnCount; ++columnIndex) {
  71. final String propertyName = JdbcUtils.convertUnderscoreNameToPropertyName(
  72. JdbcUtils.lookupColumnName(metaData, columnIndex));
  73. columnPropertyToIndexMap.put(propertyName, columnIndex);
  74. }
  75. return columnPropertyToIndexMap;
  76. }
  77. private static record Arg(int order, String name, Class<?>type) {
  78. }
  79. }
展开查看全部
cxfofazt

cxfofazt2#

您可以使用DataClassRowMapper。它可以很好地处理唱片。

  1. @Override
  2. public City findById(Long id) {
  3. String sql = "SELECT * FROM cities WHERE id = ?";
  4. return jtm.queryForObject(sql, new DataClassRowMapper<>(City.class), id);
  5. }

相关问题