Spring-ws+MyBatis中自定义TypeHandler如何匹配对应ObjectFactory?
Great question! Let's break down your two main issues and solve them step by step:
When Spring-WS generates POJOs from your XSD, it automatically creates an ObjectFactory class in the same package as your generated beans. This factory includes dedicated methods to create JAXBElement instances for every nillable element defined in your XSD. For your numericParam field, the generated method will look something like this:
public JAXBElement<Integer> createNumericParam(Integer value) { return new JAXBElement<>(new QName("your-target-namespace", "numericParam"), Integer.class, null, value); }
Option 1: Use the Generated ObjectFactory
You can directly instantiate this generated ObjectFactory in your TypeHandler. Make sure to replace com.your.generated.package with the actual package where your Spring-WS generated code lives:
import com.your.generated.package.ObjectFactory; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import javax.xml.bind.JAXBElement; import java.sql.*; public class JAXBElementIntegerTypeHandler extends BaseTypeHandler<JAXBElement<Integer>> { private final ObjectFactory jaxbFactory = new ObjectFactory(); @Override public void setNonNullParameter(PreparedStatement ps, int i, JAXBElement<Integer> parameter, JdbcType jdbcType) throws SQLException { Integer value = parameter != null ? parameter.getValue() : null; ps.setInt(i, value != null ? value : 0); // Adjust null handling based on your DB requirements } @Override public JAXBElement<Integer> getNullableResult(ResultSet rs, String columnName) throws SQLException { Integer value = rs.getInt(columnName); if (rs.wasNull()) { return jaxbFactory.createNumericParam(null); } return jaxbFactory.createNumericParam(value); } // Implement other getNullableResult overloads similarly }
Option 2: Manually Instantiate JAXBElement
If you don't want to depend on the generated ObjectFactory, you can directly construct the JAXBElement yourself. You'll just need to match the QName to your XSD's target namespace and element name:
import javax.xml.namespace.QName; // ... other imports public class JAXBElementIntegerTypeHandler extends BaseTypeHandler<JAXBElement<Integer>> { // Replace with your actual XSD target namespace and element name private static final QName NUMERIC_PARAM_QNAME = new QName("http://your-ws-namespace.com", "numericParam"); @Override public JAXBElement<Integer> getNullableResult(ResultSet rs, String columnName) throws SQLException { Integer value = rs.getInt(columnName); if (rs.wasNull()) { return new JAXBElement<>(NUMERIC_PARAM_QNAME, Integer.class, null); } return new JAXBElement<>(NUMERIC_PARAM_QNAME, Integer.class, value); } // Implement other methods similarly }
MyBatis doesn't directly expose the calling Mapper in TypeHandler methods by default, but you can use a MyBatis Interceptor to capture this information and store it in a ThreadLocal for your TypeHandler to access. Here's how:
Step 1: Create an Interceptor to Track MappedStatement IDs
The MappedStatement ID follows the format fully-qualified-mapper-class-name.method-name, so we can extract both the Mapper class and method from it:
import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import java.util.Properties; @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class MapperTrackingInterceptor implements Interceptor { private static final ThreadLocal<String> CURRENT_STATEMENT_ID = new ThreadLocal<>(); // Expose the current statement ID for TypeHandler to access public static String getCurrentStatementId() { return CURRENT_STATEMENT_ID.get(); } @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms = (MappedStatement) invocation.getArgs()[0]; CURRENT_STATEMENT_ID.set(ms.getId()); try { return invocation.proceed(); } finally { CURRENT_STATEMENT_ID.remove(); // Clean up to avoid memory leaks } } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) {} }
Step 2: Access the Mapper Info in Your TypeHandler
Update your TypeHandler to retrieve the statement ID from the interceptor and parse out the Mapper class and method:
import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import javax.xml.bind.JAXBElement; import java.sql.*; public class JAXBElementIntegerTypeHandler extends BaseTypeHandler<JAXBElement<Integer>> { private final ObjectFactory jaxbFactory = new ObjectFactory(); @Override public void setNonNullParameter(PreparedStatement ps, int i, JAXBElement<Integer> parameter, JdbcType jdbcType) throws SQLException { // Get the current statement ID from the interceptor String statementId = MapperTrackingInterceptor.getCurrentStatementId(); if (statementId != null) { // Parse Mapper class name and method name String mapperClassName = statementId.substring(0, statementId.lastIndexOf('.')); String methodName = statementId.substring(statementId.lastIndexOf('.') + 1); System.out.println("Called by Mapper: " + mapperClassName + ", Method: " + methodName); // You can log this or use it for custom logic } // Handle parameter setting Integer value = parameter != null ? parameter.getValue() : null; ps.setInt(i, value != null ? value : 0); } // Implement other methods as before }
Step 3: Register the Interceptor with MyBatis
If you're using Spring Boot, register the interceptor in your SqlSessionFactory configuration:
import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class MyBatisConfig { @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); // Add the interceptor factoryBean.setPlugins(new MapperTrackingInterceptor()); return factoryBean.getObject(); } }
This approach is stable and doesn't rely on internal MyBatis APIs that might change between versions.
内容的提问来源于stack exchange,提问作者Karol159

