我们在 MS SQL 中有一个存储过程,它返回一些与任何数据库表都不匹配的结果集。我们希望在不使用 @Entity 注释的情况下将结果映射到普通的 java POJO。但是如果我们打电话
entityManager.createStoredProcedureQuery("schema.myProcedure",
PlainPojo.class);
它将失败
org.hibernate.MappingException: Unknown entity
异常(exception)我知道有 @SqlResultSetMapping 注释,但它需要放在 Entity 类上?这不起作用
@Repository
@SqlResultSetMapping(name = "Mapping")
public class MyRepositoryImpl implements MyRepository
只有这个
@Enity
@SqlResultSetMapping(name = "Mapping")
public class MyEntity
但我们不想要实体类...所以把它放在另一个实体上?? (不完全是)
另一种解决方案?
最佳答案
我没有在 stackoverflow 上找到复杂的解决方案并重新发明轮子,也许这对需要在项目中多次调用存储过程的人有所帮助。
主要思想是编写类似于@StoredProcedure的自定义注解,但独立于@Entity
@Target(TYPE)
@Retention(RUNTIME)
public @interface CustomNamedStoredProcedureQueries {
/**
* Array of CustomNamedStoredProcedureQuery annotations.
*/
CustomNamedStoredProcedureQuery[] value();
}
@Target(TYPE)
@Retention(RUNTIME)
public @interface CustomNamedStoredProcedureQuery {
/*
* The name of the stored procedure for call.
*/
String name();
/*
* The name of the stored procedure in the database.
*/
String procedureName();
/*
* The scheme name of the database.
*/
String schemeName();
/*
* The name of the package that contains stored procedure in the database.
*/
String packageName();
/*
* Information about all parameters of the stored procedure.
*/
CustomStoredProcedureParameter[] parameters() default {};
/*
* The names of one or more result set mappings, as defined in metadata, when you need custom mapping.
*/
ProcedureRowMapper[] resultSetRowMappers() default {};
/*
* The names of one or more result set mappings, as defined in metadata, when you need 1 to 1 mapping.
*/
ProcedureResultClass[] resultSetMappers() default {};
}
@Target({})
@Retention(RUNTIME)
public @interface CustomStoredProcedureParameter {
/** Name of strored procedure parameter.*/
String name() default "";
/** JDBC type of the parameter.*/
int type();
/** Parameter mode.*/
ParameterMode mode() default ParameterMode.IN;
}
@Target({})
@Retention(RUNTIME)
public @interface ProcedureResultClass {
/* Name of procedure parameter*/
String name();
/* Class of object using for mapping */
Class<?> resultClass();
}
@Target({})
@Retention(RUNTIME)
public @interface ProcedureRowMapper {
/* Name of cursor field for use mapper*/
String name();
/* Class of object using for mapping, must extend BeanPropertyRowMapper */
Class<? extends BeanPropertyRowMapper> mapper();
}
如前所述,当您需要将 resultSet 1 映射到 1 时,请使用 ProcedureResultClass,字段必须具有相同的名称。
当你需要一些自定义映射时使用 ProcedureRowMapper,你可以@Override mapRow() 方法,并手动映射它,或者您可以@Override initBeanWrapper() 如果您只需要以特定方式转换某些字段。
所以,接下来我们需要一个让它工作的方法。把它放在你的应用程序的 init 方法中,比如 main 方法的 @PostConstruct 。
ProjectUtils {
private JdbcTemplate jdbcTemplate;
@Autowired
public ProjectUtils(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(DataSource);
}
private static Map<String, SimpleJdbcCall> storedProcedureContainer = new HashMap<>();
public void initStoredProcedure() {
Reflections reflections = new Reflections("my.package");
Set<Class<?>> classList = reflections.getTypesAnnotatedWith(CustomNamedStoredProcedureQueries.class);
for (Class<?> clazz : classList) {
CustomNamedStoredProcedureQueries queriesAnnotation = clazz.getAnnotation(CustomNamedStoredProcedureQueries.class);
for (CustomNamedStoredProcedureQuery queryAnnotation : queriesAnnotation.value()) {
SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate)
.withSchemaName(queryAnnotation.schemeName())
.withCatalogName(queryAnnotation.packageName())
.withProcedureName(queryAnnotation.procedureName());
Set<SqlParameter> parameters = this.getDeclaredParameters(queryAnnotation.parameters());
jdbcCall.declareParameters(parameters.toArray(new SqlParameter[0]));
Map<String, RowMapper<?>> rowMappers = getDeclaredRowMappers(queryAnnotation.resultSetRowMappers(), queryAnnotation.resultSetMappers());
rowMappers.forEach((parameterName, rowMapper) -> jdbcCall.addDeclaredRowMapper(parameterName, rowMapper));
jdbcCall.compile();
storedProcedureContainer.put(queryAnnotation.name(), jdbcCall);
}
}
}
private Set<SqlParameter> getDeclaredParameters(CustomStoredProcedureParameter[] procedureParameters) {
Set<SqlParameter> parameters = new HashSet<>();
for (CustomStoredProcedureParameter procedureParameter : procedureParameters) {
if (procedureParameter.mode().equals(ParameterMode.IN)) {
parameters.add(new SqlParameter(procedureParameter.name(), procedureParameter.type()));
}
if (procedureParameter.mode().equals(ParameterMode.OUT)) {
parameters.add(new SqlOutParameter(procedureParameter.name(), procedureParameter.type()));
}
}
return parameters;
}
private Map<String, RowMapper<?>> getDeclaredRowMappers(ProcedureRowMapper[] resultSetMappers, ProcedureResultClass[] simpleResultSetMappers) {
Map<String, RowMapper<?>> mappers = new HashMap<>();
if (!ArrayUtils.isEmpty(simpleResultSetMappers)) {
for (ProcedureResultClass procedureResultClass : simpleResultSetMappers) {
mappers.put(procedureResultClass.name(), BeanPropertyRowMapper.newInstance(procedureResultClass.resultClass()));
}
return mappers;
}
try {
for (ProcedureRowMapper resultSetMapper : resultSetMappers) {
mappers.put(resultSetMapper.name(), resultSetMapper.mapper().newInstance());
}
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return mappers;
}
public SimpleJdbcCall getProcedure(String name) {
return storedProcedureContainer.get(name);
}
}
示例 (我使用 oracledb,但稍作改动,它可以用于另一个数据库)
@CustomNamedStoredProcedureQueries({
@CustomNamedStoredProcedureQuery(
name = "getFoo",
schemeName = "FooScheme",
packageName = "FooPackage",
procedureName = "get_Foo",
resultSetMappers = @ProcedureResultClass(name = "foo_cursor", resultClass = Foo.class),
parameters = {
@CustomStoredProcedureParameter(name = "foo_id", type = OracleTypes.NUMBER, mode = ParameterMode.IN),
@CustomStoredProcedureParameter(name = "error_code", type = OracleTypes.NUMBER, mode = ParameterMode.OUT),
@CustomStoredProcedureParameter(name = "foo_cursor", type = OracleTypes.CURSOR, mode = ParameterMode.OUT),
}
),
@CustomNamedStoredProcedureQuery(
name = "deleteFoo",
schemeName = "FooScheme",
packageName = "FooPackage",
procedureName = "delete_Foo",
parameters = {
@CustomStoredProcedureParameter(name = "foo_id", type = OracleTypes.NUMBER, mode = ParameterMode.IN),
@CustomStoredProcedureParameter(name = "error_code", type = OracleTypes.NUMBER, mode = ParameterMode.OUT),
}
)
})
public class Foo {
long id;
String name;
int age;
public void setId(long id) {
this.id= id;
}
public long getId() {
return this.id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
}
叫它:
public Foo getFoo(Long fooId) {
SqlParameterSource sqlParams = new MapSqlParameterSource()
.addValue("foo_id", fooId);
SimpleJdbcCall procedure = projectUtils.getProcedure("getFoo");
Map<String, Object> result = procedure.execute(sqlParams);
return ((List<Foo>) result.get("foo_cursor")).get(0);
}
提示 - 它可以与自定义类型(存储在 bd 中的类型)一起使用,例如 Array,因为您需要创建扩展的 AbstractSqlTypeValue 的自定义类型并覆盖 createTypeValue() ,然后像其他参数一样传递它( 仅在 oracle 上经过仔细测试)。
CustomType 必须在方案级别和更高级别上声明,在包级别它不起作用,方案和类型必须以大写形式调用。
@Override
protected Object createTypeValue(Connection connection, int sqlType, String typeName) throws SQLException {
if (connection.isWrapperFor(OracleConnection.class)) {
OracleConnection oracleConnection = connection.unwrap(OracleConnection.class);
return oracleConnection.createARRAY("FOOSCHEME.NUMBERARRAY", this.array); // array pass when customType class init
}
return connection.createArrayOf("FOOSCHEME.NUMBERARRAY", this.array);
}
关于java - Spring/Hibernate - 在 Java 中不使用 @Entity 从存储过程中获取结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48687462/