mybatis异步操作数据库

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/baidu_23086307/article/details/82684558

很多时候我们需要用mybatis来做数据库orm,基于mybatis 优秀的基因,我们能够轻松的搞定数据库的orm。
但是mybatis一般的使用方法都是一个同步查询,主线程调用数据库查询操作,等待返回结果,这样在高并发网络情况下代价是很高的。所以我们需要封装一套提供异步查询回调机制。

异步操作。提到异步操作,我们就得提到回调接口。回调接口就是通过在主线程监听其他线程执行完的结果取得返回值。或者做一些逻辑处理,常用的接口在jdk提供了Future,Callable等机制,不过一个是阻塞,一个异步,所以我们常用的是Callable机制。
下面我们来手动写这么一套封装框架,实现异步操作。
首先我们想要的是这种机制:

mapper.method(parm,parm,new 回调函数(){
  success(data{}
  error(erroe){}
}

            }));

我们能够在回调函数里面获取到执行结果。所以我们要将结果写入到回调函数里面,基于mybatis 的mapper机制。sql语句利用xml表示,通过id于方法名称对应起来。所以我们想要异步的话就必须获取到这个mapper的代理类。通过代理类构造一个代理对象,传入方法名称,参数,然后执行代理方法。然后将代理任务提交给mybatis去执行sql语句,最后将结果返回给回调函数。

所以我们有了大体思路。我们通过大概的流程图来描绘一下。

这里写图片描述

首先我们需要一个执行数据库操作的线程,我们通过线程池的模式来管理线程。要是对线程池不熟悉的小伙伴,还希望自行补脑。

如下代码所示:

/**
 * 异步mapper 提交任务执行器
 *
 * @author twjitm - [Created on 2018-09-11 11:02]
 * @company http://www.twjitm.com/
 * @jdk java version "1.8.0_77"
 */
public class AsyncMapperExecutor {
    private static int MAX_QUEUE_SIZE;

    private static int CORE_POOL_SIZE;

    private static int MAX_POOL_SIZE;

    private static String THREAD_NAME;

    private static volatile ThreadPoolExecutor executorService = null;

    /**
     * 初始化异步db执行线程
     *
     * @param corePoolSize
     * @param maxPoolSize
     * @param queueSize
     */
    public static void init(int corePoolSize, int maxPoolSize, int queueSize,String threadName) {
        CORE_POOL_SIZE = corePoolSize;
        MAX_POOL_SIZE = maxPoolSize;
        MAX_QUEUE_SIZE = queueSize;
        THREAD_NAME=threadName;
        init();
    }


    public static void init() {
        if (executorService == null) {
            executorService = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, 60L, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(MAX_QUEUE_SIZE), new ThreadFactory() {
                private AtomicInteger counter = new AtomicInteger(1);

                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r);
                    t.setName(THREAD_NAME + counter.getAndIncrement());
                    t.setDaemon(false);
                    return t;
                }
            });
        }
    }

    /**
     * 异步执行一个任务
     *
     * @param mapper
     * @param method
     * @param args
     * @param callback
     */
    public static void executeRunnable(Object mapper, Method method, Object[] args, TaskBackHandler callback) {
        checkNull();
        AsyncMapperTask executor = new AsyncMapperTask(mapper, method, args, callback);
        executorService.execute(executor);
    }


    private static void checkNull() {
        if (executorService == null) {
            throw new AsyncMapperException("数据库异步执行线程需要初始化");
        }
    }
}

此类有两个功能,一个是负责初始化线程池,另一个就是提供执行线程池操作。通过提交的代理参数,初始化一个异步任务,最后提交。

接下来我们需要一个生产mapper的工厂,其实就是对mybats提供的接口进行封装。

/**
 * 不要轻易去生产一个{@link Connection}对象。
 * <p>
 * sql session 工厂
 *
 * @author twjitm - [Created on 2018-09-10 18:40]
 * @company http://www.twjitm.com/
 * @jdk java version "1.8.0_77"
 */
public class NettySqlSessionFactory {
    private static  SqlSessionFactory SQL_SESSION_FACTORY;

    public  static void  init(String resource){
       // String resource = "db/mybatis-config.xml";
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader(resource);
        } catch (IOException e) {
            System.out.println(e.getMessage());

        }
        SQL_SESSION_FACTORY = new SqlSessionFactoryBuilder().build(reader);
    }

    /**
     * 具体请查看mybatis 的config
     *
     * @return
     */
    public static SqlSession getSqlSession() {
        return SQL_SESSION_FACTORY.openSession(true);
    }


有了它,我们就能够连接上数据库,因此我们还缺一个动态代理对象,用来代理我们自定义的mapper接口类。

/**
 * @author twjitm - [Created on 2018-09-11 10:52]
 * @company http://www.twjitm.com/
 * @jdk java version "1.8.0_77"
 */
public class AsyncMapperObjectProxy<T> implements InvocationHandler {

    private T mapper;

    public AsyncMapperObjectProxy(T mapper) {
        this.mapper = mapper;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
        if (isCallbackMethod(method, params)) {
            TaskBackHandler<T> handler = null;
            for (Object param : params) {
                if (param != null) {
                    if (TaskBackHandler.class.isAssignableFrom(param.getClass())) {
                        handler = (TaskBackHandler<T>) param;
                    }
                }
            }
            AsyncMapperExecutor.executeRunnable(mapper, method, params, handler);
            return null;
        } else {
            return method.invoke(mapper, params);
        }
    }
    /**
     * 是否为异步调用mapper方法
     *
     * @param method
     * @param args
     * @return
     */
    private boolean isCallbackMethod(Method method, Object[] args) {
        boolean hasAsyncCallback = false;

        if (args == null) {
            return false;
        }

        int count = 0;
        for (Object arg : args) {
            if (arg != null) {
                if (TaskBackHandler.class.isAssignableFrom(arg.getClass())) {
                    hasAsyncCallback = true;
                    count++;
                }
            }
        }

        if (hasAsyncCallback && count > 1) {
            throw new AsyncMapperException(
                    "Method[" + method.getName() + "] has more than one callback" +
                            " method!");
        }

        return hasAsyncCallback;
    }

}

有了它,我们能够代理得到一个mapper对象了,因此我们为了代理简洁,我们需要一个mapper代理工厂,工厂主要是获取一个mapper对象。代码很简单,如下所示。

/**
 * mapper bean 工厂
 *
 * @author twjitm - [Created on 2018-09-11 11:26]
 * @company http://www.twjitm.com/
 * @jdk java version "1.8.0_77"
 */
public class MapperBeanFactory {


    /**
     * 通过代理,获取一个mapper对象
     *
     * @return
     * @throws Exception
     */
    public static Object getMapperBean(Class mapperInterface) throws Exception {
        Object mapper = NettySqlSessionFactory.getSqlSession().getMapper(mapperInterface);
        AsyncMapperObjectProxy asyncMapperObjectProxy = new AsyncMapperObjectProxy<>(mapper);
        return Proxy.newProxyInstance(mapperInterface.getClassLoader(),
                new Class[]{mapperInterface}, asyncMapperObjectProxy);
    }
}

最后我们需要一个异步任务对象

/**
 * @author twjitm - [Created on 2018-09-11 11:16]
 * @company http://www.twjitm.com/
 * @jdk java version "1.8.0_77"
 */
public class AsyncMapperTask<T> implements Runnable {
    Logger logger = LoggerFactory.getLogger(AsyncMapperTask.class);
    private TaskBackHandler<T> callback;

    private Object mapper;

    private Method method;

    private Object[] args;

    public AsyncMapperTask(Object mapper, Method method, Object[] args, TaskBackHandler<T> callback) {
        this.mapper = mapper;
        this.method = method;
        this.args = args;
        this.callback = callback;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void run() {
        T result;
        try {
            result = (T) method.invoke(mapper, args);
            callback.taskBack( true,result);
        } catch (Exception e) {
            // 仅仅捕获数据库访问异常
            logger.error("执行数据库查询发生异常", e);
            try {
                callback.taskBack(false, null);
            } catch (Exception e1) {
                e1.printStackTrace();
                logger.error("执行数据库查询发生异常", e1);
            }
        }
    }
}

有了以上代码的功能,我们能够异步查询了,因此我们来测试一下。首先我们添加mybatis的基本配置文件。

mybatis-config.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>
    <!--加载配置文件-->
    <properties resource="db/db_jdbc.properties"/>


    <settings>
        <setting name="cacheEnabled" value="true"/>
        <!-- 打开延迟加载的开关 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--开启打印日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>

        <setting name="multipleResultSetsEnabled" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <setting name="defaultStatementTimeout" value="25"/>
        <!--允许嵌套语句中是否使用分页-->
        <setting name="safeRowBoundsEnabled" value="false"/>

        <setting name="mapUnderscoreToCamelCase" value="false"/>
        <!--一级缓存-->
        <setting name="localCacheScope" value="SESSION"/>
        <setting name="jdbcTypeForNull" value="OTHER"/>
        <!--懒加载方法-->
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>

    </settings>
    <typeAliases>
        <!--
           type:类型路径
           alias:别名

       <typeAlias type="cn.twjitm.mybatis.po.User" alias="user"/>
        -->
        <!-- 批量别名定义
            指定包名:mybatis自动扫描包中的po类,自动定义别名
            别名就是类名(首字母大小写都可以)
         -->

        <typeAlias alias="User" type="com.twjtim.server.core.mybatis.User"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <!--使用jdbc事务机制-->
            <transactionManager type="jdbc"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/db_0?characterEncoding=utf8&amp;useSSL=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>

                <!--连接池配置-->
                <property name="poolMaximumIdleConnections" value="0"/>
                <property name="poolMaximumActiveConnections" value="10000"/>
                <property name="poolPingQuery" value="SELECT 1 FROM DUAL"/>
                <property name="poolPingEnabled" value="true"/>

            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 加载单个mapper -->
        <mapper resource="db/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

然后我们在数据库创建一张user表;
这里写图片描述

表结构很简单,足以说明问题。

然后我们通过逆向生成代码也行,手写mapper.xml文件也想,我相信若是看到这篇文章的人应该都会逆向生成的吧,
mybatis+generator[传送门:https://www.cnblogs.com/yjmyzz/p/mybatis-generator-tutorial.html]
我们得到User这样的一个类和一个Usermapper.xml和UserMapper.java这样的一个文件,放到正确的位置。然后我们就改写
UserMapper.java 这个类了
我们想要一部操作,首先我们要给每个方法注入一个异步接口。 异步接口通过封装,一个方法足够调用了。

/**
 * 异步 mybatis 的mapper方法执行成功或失败的回调函数
 *
 * @author twjitm - [Created on 2018-09-11 10:52]
 * @company http://www.twjitm.com/
 * @jdk java version "1.8.0_77"
 */
public interface TaskBackHandler<T> {

    /**
     * 执行成功
     *
     * @param result
     */
    void taskBack(final boolean successful,T result) throws Exception;


}

在UserMapper.java 这个类中我们对方法添加上异步接口,如下代码

public interface UserMapper {


    void getUser(String name,TaskBackHandler<List<User>> callback);


    void insertUser(User user,TaskBackHandler<User> callback);

    void getUserByName(String name,Integer age,TaskBackHandler<User> callback);

}

这里写图片描述

最后我们写一个测试类来测试一下

public class TestMybatis {


    public static void main(String[] args) {

        DbServiceConfig.init("db/mybatis-config.xml");

        //异步回调接口
        try {
            UserMapper userMapper = (UserMapper) MapperBeanFactory.getMapperBean(UserMapper.class);
            userMapper.getUserByName("tangwenjiang",23,((successful, result) -> {
                if(result!=null){
                    System.out.println( result.getName());
                }

            }));
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("主线程后面的逻辑");
    }

}

日志

这里写图片描述

源码下载:https://download.csdn.net/download/baidu_23086307/10663878

阅读更多

扫码向博主提问

twjitm

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • java
  • spring
  • mybatis
  • hibernate
  • javaweb
去开通我的Chat快问
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页