Jan*_*nar 3 java spring-boot java-14 java-record
我正在使用新的 Java 14 和 Spring Boot。对于数据持有者,我使用了新的很酷的记录,而不是常规的 Java 类。
public record City(Long id, String name, Integer population) {}
Run Code Online (Sandbox Code Playgroud)
稍后在我的服务类中,我使用 SpringBeanPropertyRowMapper来获取数据。
@Override
public City findById(Long id) {
String sql = "SELECT * FROM cities WHERE id = ?";
return jtm.queryForObject(sql, new Object[]{id},
new BeanPropertyRowMapper<>(City.class));
}
Run Code Online (Sandbox Code Playgroud)
我最终出现以下错误:
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>()
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:145) ~[spring-beans-5.2.3.RELEASE.jar:5.2.3.RELEASE]
Run Code Online (Sandbox Code Playgroud)
如何为记录添加默认构造函数或有其他方法可以解决此问题?
ete*_*ech 12
您可以使用DataClassRowMapper。它与记录配合得很好。
@Override
public City findById(Long id) {
String sql = "SELECT * FROM cities WHERE id = ?";
return jtm.queryForObject(sql, new DataClassRowMapper<>(City.class), id);
}
Run Code Online (Sandbox Code Playgroud)
只需通过为字段提供默认值来显式声明它:
public record City(Long id, String name, Integer population) {
public City() {
this(0L, "", 0)
}
}
Run Code Online (Sandbox Code Playgroud)
一个重要的注意事项。BeanPropertyRowMapper扫描 setter/getter 以扩充您的记录实例,因为记录是不可变的,没有 setter 并且它与 java bean 规范不兼容,您将获得并清空记录。请阅读此SO。创建记录的唯一方法是使用构造函数。因此,您有两个选择:使用普通的 java bean 或实现您的自定义行映射器。
最简单的方式是这样的:
@Override
public City findById(final Long id) {
final var sql = "SELECT * FROM cities WHERE id = ?";
return jtm.queryForObject(
sql,
new Object[]{ id },
(rs, rowNum) -> new City(
rs.getLong("id"),
rs.getString("name"),
rs.getInt("population")));
}
Run Code Online (Sandbox Code Playgroud)
或者您可以使用反射:
反射API
以下公共方法将被添加到 java.lang.Class 中:
Run Code Online (Sandbox Code Playgroud)RecordComponent[] getRecordComponents() boolean isRecord()getRecordComponents() 方法返回一个 java.lang.reflect.RecordComponent 对象数组,其中 java.lang.reflect.RecordComponent 是一个新类。此数组的元素对应于记录的组件,其顺序与它们在记录声明中出现的顺序相同。可以从数组中的每个 RecordComponent 中提取附加信息,包括其名称、类型、泛型类型、注释及其访问器方法。
如果给定的类被声明为记录,则方法 isRecord() 返回 true。(与 isEnum() 比较。)
使用这些方法和Class#getConstructor(Class... parameterTypes)和Constructor#newInstance(Object... initargs)您可以动态创建记录。但请记住,反射可能会带来一些开销并影响您的性能。
我使用反射和几个测试添加了一个RecordRowMapper示例:
package by.slesh.spring.jdbc.core;
import org.springframework.jdbc.IncorrectResultSetColumnCountException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.JdbcUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.RecordComponent;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;
public class RecordRowMapper<T> implements RowMapper<T> {
private final Constructor<T> ctor;
private final List<Arg> args;
public RecordRowMapper(final Class<T> model) {
if (!model.isRecord()) {
throw new IllegalArgumentException(
model + " should be a record class");
}
final RecordComponent[] components = model.getRecordComponents();
this.args = new ArrayList<>(components.length);
final Class<?>[] argTypes = new Class[components.length];
for (int i = 0; i < components.length; ++i) {
final RecordComponent c = components[i];
this.args.add(new Arg(i, c.getName(), c.getType()));
argTypes[i] = c.getType();
}
try {
this.ctor = model.getConstructor(argTypes);
} catch (NoSuchMethodException e) {
throw new RuntimeException(
"Couldn resolve constructor for types " + Arrays.toString(argTypes));
}
}
@Override
public T mapRow(final ResultSet resultSet, final int rowNumber) throws SQLException {
final var metaData = resultSet.getMetaData();
final int columnCount = metaData.getColumnCount();
if (columnCount < args.size()) {
throw new IncorrectResultSetColumnCountException(
args.size(), columnCount);
}
try {
return ctor.newInstance(extractCtorParams(
resultSet, createPropertyToColumnIndexMap(
metaData, columnCount)));
} catch (InstantiationException
| IllegalAccessException
| InvocationTargetException e) {
throw new RuntimeException(e);
}
}
private Object[] extractCtorParams(
final ResultSet resultSet,
final Map<String, Integer> propertyToColumnIndexMap)
throws SQLException {
final var params = new Object[args.size()];
for (final var arg : args) {
final int columnIndex = propertyToColumnIndexMap.get(arg.name);
params[arg.order] = JdbcUtils.getResultSetValue(
resultSet, columnIndex, arg.type);
}
return params;
}
private Map<String, Integer> createPropertyToColumnIndexMap(
final ResultSetMetaData metaData,
final int columnCount)
throws SQLException {
final Map<String, Integer> columnPropertyToIndexMap = new HashMap<>(columnCount);
for (int columnIndex = 1; columnIndex <= columnCount; ++columnIndex) {
final String propertyName = JdbcUtils.convertUnderscoreNameToPropertyName(
JdbcUtils.lookupColumnName(metaData, columnIndex));
columnPropertyToIndexMap.put(propertyName, columnIndex);
}
return columnPropertyToIndexMap;
}
private static record Arg(int order, String name, Class<?>type) {
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
855 次 |
| 最近记录: |