Mybatis 参数化现实多数据源

330 阅读5分钟

Mybatis 参数化现实多数据源

​ 首先我们知道Mybatis可以利用配置文件实现多数据源的配置,且可以实现数据的切换。

​ 但是我在项目中遇到一个问题是:数据源的地址、端口等一些列信息,甚至表都要需要动态获取,也就是说不能先直接在配置文件配置。

​ 我这里的解决办法是:动态生成XML配置文件的方式配置数据源,并且配置化操作某一个张表。当前操作的表还不多,可以这样做。如果直接操作一个库,那肯定是要用多数据配置文件的方式了。

​ 在项目Mybatis、数据源等依赖引入之后,我们需要多引入下面一个依赖,来实现构建XML文件:

​ (这里本人主要也是学习一下 XML文件和Java对象之间的转换工具XStream

<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.4.10</version>
</dependency>

一、正常操作示例

整体目录结构:

image-20230330152521308.png

1.1 建表语句

CREATE TABLE `person` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(64) DEFAULT NULL,
  `sex` char(4) DEFAULT NULL,
  KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=127 DEFAULT CHARSET=utf8mb3;

insert into `person`(`id`,`name`,`sex`) 
values (101,'王麻子','男'),
	   (102,'娜美','女'),
	   (103,'娜美','女'),
	   (104,'娜美','女'),
	   (105,'小明','男'),
	   (107,'小明','男');

1.2 Mapper

@Data
public class Person implements Serializable {
    private Integer id;
    private String name;
    private String sex;
}
@Mapper
public interface PersonMapper {
    List<Person> queryList(@Param("sex")String sex);
}

PersonMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yogurt.example.dao.PersonMapper">
    <resultMap id="BaseResultMap" type="com.yogurt.example.entity.Person">
        <result column="id" jdbcType="INTEGER" property="id" />
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="sex" jdbcType="CHAR" property="sex" />
    </resultMap>

    <select id="queryList" parameterType="java.lang.String" resultType="com.yogurt.example.entity.Person">
        select * from ${tableName}
        where sex = #{sex}
    </select>
</mapper>

1.3 配置文件

ThirdMybatisConfig.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
                <property name="url" value="jdbc:mysql://localhost:3306/yogurtlearn?useUnicode=true&amp;useJDBCCompliantTimezoneShift=true&amp;useLegacyDatetimeCode=false&amp;serverTimezone=UTC"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mybatis/PersonMapper.xml"/>
    </mappers>
</configuration>

1.4 查询步骤

public class ThirdMybatisConfigTest {
    @Test
    public void testMyBatisBuild() throws IOException {
        // 1、获取配置文件流;
        InputStream input = Resources.getResourceAsStream("mybatis/ThirdMybatisConfig.xml");

        // 2、解析XML配置文件,构建 SqlSessionFactory;
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(input);
        // 3、利用SqlSessionFactory创建 SqlSession 会话;
        SqlSession sqlSession = sessionFactory.openSession();

        // 4、利用 SqlSession 创建 Mapper接口 的代理对象 MapperProxy;
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
        // 5、然后在执行 Mapper接口 的 SQL查询时,转而利用代理对象执行 JDBC的SQL底层操作。
        List<Person> persons = mapper.queryList("person","男");
    }
}

二、数据源参数化改造

我们主要就是改造查询步骤的第一步,获取配置文件流,正常都是通过路径找到的;这里我们直接通过构造xml配置文件生成配置文件流。

利用 XStream工具 让Java对象转xml配置文件;构建配置文件Configuration对应的Java对象。

新增三个文件,实现对象转XML:

image-20230330162639311.png

2.1 构建Configuration对象

@Data
@XStreamAlias("configuration") // 给XML标签起个别名
public class Configuration {
    public Environments environments = new Environments();
    public Mappers mappers = new Mappers();;
    public Configuration() {
    }

    @Data
    @XStreamAlias("mappers")  // mappers标签
    public static class Mappers{
        @XStreamImplicit // mapper数组
        private List<Mapper> mapper = new ArrayList<Mapper>();;

        public void addMapper(Mapper mapper) {
            this.mapper.add(mapper);
        }

        @Data
        @XStreamAlias("mapper")
        public static class Mapper{
            @XStreamAlias("resource")  // mapper标签上的属性 resource
            @XStreamAsAttribute
            private String resource;

            public Mapper(String resource) {
                this.resource = resource;
            }
        }
    }

    @Data
    @XStreamAlias("environments")  // environments 环境标签
    public static class Environments {
        @XStreamAlias("default")   //  environments 环境标签上的属性 default
        @XStreamAsAttribute
        private String environments;

        private Environment environment;

        public Environments() {
            this.environments = "development";  // 环境默认 development
            this.environment = new Environment();
        }

        @Data
        @XStreamAlias("environment")
        public static class Environment {
            @XStreamAlias("id")
            @XStreamAsAttribute
            private String environment;
            private TransactionManager transactionManager;
            private DataSource dataSource;

            public Environment() {
                this.environment = "development";
                this.transactionManager  = new TransactionManager();
                this.dataSource = new DataSource();
            }

            @Data
            @XStreamAlias("transactionManager")  // 事务管理器标签
            public static class TransactionManager {
                @XStreamAlias("type")
                @XStreamAsAttribute
                private String transactionManager;

                public TransactionManager() {
                    this.transactionManager = "JDBC"; // 默认JDBC类型
                }
            }

            @Data
            @XStreamAlias("dataSource")  // 数据源标签 dataSource
            public static class DataSource {
                @XStreamAlias("type")
                @XStreamAsAttribute
                private String dataSource;

                public DataSource() {
                }

                @XStreamImplicit
                private List<Property> property  = new ArrayList<Property>();

                public DataSource(String driver, String username, String password, String ip, Integer port, String database) {
                    this.dataSource = "POOLED";
                    // 这里在构造方法里面默认add了4个 数据源标签属性,其实,我们可以在new对象的时候一个一个添加。
                    this.property.add(new Property("driver",driver));
                    this.property.add(new Property("username",username));
                    this.property.add(new Property("password",password));
                    this.property.add(new Property("url","jdbc:mysql://" +ip+ ":" +port+ "/" +database+ "?characterEncoding=utf8&characterSetResults=utf8&serverTimezone=GMT%2b8&autoReconnect=true"));
                }
            }

            @Data
            @XStreamAlias("property")   // property 标签
            public static class Property {
                @XStreamAlias("name")
                @XStreamAsAttribute
                private String name;   // property标签上属性name

                @XStreamAlias("value")
                @XStreamAsAttribute
                private String value;  // property标签上属性value

                public Property(String name, String value) {
                    this.name = name;
                    this.value = value;
                }
            }
        }
    }
}

2.2 处理XML声明

/**
 * 增加XML声明,默认XStream是没有XML声明的
 */
public class XmlUtf8XStream extends XStream {
    /**
     * xml版本号,默认1.0
     */
    private String version;
    /**
     * xml编码,默认UTF-8
     */
    private String encoding;

    public XmlUtf8XStream() {
        this.version = "1.0";
        this.encoding = "UTF-8";
    }

    // XML的声明
    // 这里直接是Mybatis配置文件的声明;我们也可以通过传参的方式配置文件声明
    public String getDeclaration() {
        return "<?xml version=\"" + this.version + "\" encoding=\"" + this.encoding + "\"?>\n"
                + "<!DOCTYPE configuration PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-config.dtd\">\n";
    }

    /**
     * 覆盖父类的方法,然后调用父类的,输出的时候先输出这个XML的声明
     * @param obj
     * @param output
     */
    @Override
    public void toXML(Object obj, OutputStream output){
        try {
            String dec = this.getDeclaration();
            byte[] bytesOfDec = dec.getBytes("UTF-8");
            //先输出XML的声明
            output.write(bytesOfDec);
        } catch (Exception e) {
            throw new RuntimeException("error", e);
        }
        // 再调用父类
        super.toXML(obj, output);
    }

    @Override
    public void toXML(Object obj, Writer writer) {
        try {
            //先输出XML的声明
            writer.write(getDeclaration());
        } catch (Exception e) {
            throw new RuntimeException("error", e);
        }
        // 再调用父类
        super.toXML(obj, writer);
    }
}

2.3 获取SqlSession

public class XmlConfigUtil {
    /**
     * 获取当前数据源的 SqlSession
     */
    public static SqlSession dataQueryBase(String driver,
                                    String username,String password,
                                    String host,Integer port,String database,
                                    String xmlMapperPath){
        // 1、获取配置文件流
        InputStream input = xmlConfigInputStream(driver, username, password, host, port, database, xmlMapperPath);
        // 2、解析XML配置文件,构建 SqlSessionFactory
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(input);
        // 3、利用SqlSessionFactory创建 SqlSession 会话
        SqlSession sqlSession = sessionFactory.openSession();
        return sqlSession;
    }

    /**
     * 输入数据源信息,构建Configuration对象,生成Mybatis xml配置文件流
     */
    private static InputStream xmlConfigInputStream(String driver,
                                                   String username,String password,
                                                   String host,Integer port,String database,
                                                   String xmlMapperPath){
        XStream xStream = new XmlUtf8XStream();
        xStream.autodetectAnnotations(true);// XStream开启注解方式自动检测
        // ★构建 Mybatis xml配置文件
        String xmlConfigFile = xStream.toXML(getConfiguration(driver, username, password, host, port, database, xmlMapperPath));
        return new ByteArrayInputStream(xmlConfigFile.getBytes());
    }

    /**
     * 构建 Configuration对象
     * @param driver  com.mysql.jdbc.Driver
     * @param xmlMapperPath  ege: PersonMapper.xml -> mybatis/mapper/PersonMapper.xml
     * @return
     */
    private static Configuration getConfiguration(String driver,
                                                 String username,String password,
                                                 String host,Integer port,String database,
                                                 String xmlMapperPath) {
        Configuration configuration = new Configuration();
        Configuration.Environments.Environment.DataSource dataSource =
                new Configuration.Environments.Environment.DataSource(
                        driver,
                        username, password,
                        host, port, database);
        configuration.getEnvironments().getEnvironment().setDataSource(dataSource);
        Configuration.Mappers.Mapper mapper = new Configuration.Mappers.Mapper(xmlMapperPath);
        configuration.getMappers().addMapper(mapper);
        return configuration;
    }
}

2.4 查询步骤

这里我们就可以参数化配置数据源信息了,然后获取到SqlSession对象,对数据库进行操作。

public class ThirdMybatisConfigTest {
    @Test
    public void testXmlConfigUtil() throws IOException {
        // 1~3步骤在这里完成
        SqlSession sqlSession = XmlConfigUtil.dataQueryBase("com.mysql.jdbc.Driver",
                                    "root", "123456",
                                    "127.0.0.1", 3306, "yogurtlean",
                                    "mybatis/mapper/PersonMapper.xml");
        // 4、利用 SqlSession 创建 Mapper接口 的代理对象 MapperProxy;
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
        // 5、然后在执行 Mapper接口 的 SQL查询时,转而利用代理对象执行 JDBC的SQL底层操作。
        List<Person> persons = mapper.queryList("person","男");
    }
}

引用: