You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring-ws+MyBatis中自定义TypeHandler如何匹配对应ObjectFactory?

Great question! Let's break down your two main issues and solve them step by step:

1. Using the Correct ObjectFactory for JAXBElement

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
}
2. Identifying Which Mapper is Calling Your TypeHandler

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:52:16