一 背景介绍
本文主要是将dw-框架引入mybatis-plus。
二 依赖引入
这里采用Druid数据库连接池(可更换)
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.8</version> </dependency>
三 mybatisBundle的加载
主要通过下面三个类来完成加载:
(1)MapperFactory主要负责Mapper文件的生成并注入HK2容器
(2)MybatisBundle负责与DW交互,让DW启动的时候加载MapperFactory
(3)SqlSessionTemplate主要是SqlSession的代理
详细介绍:
3.1 DW如何加载mybatis
DW自身有一个扩展点,可以通过实现ConfiguredBundle接口
public class AppInit implements IOCApplicationInitialization { private static final Logger LOG = LoggerFactory.getLogger(AppInit.class); @Override public <T extends Configuration> void initialize(Bootstrap<T> bootstrap) { List<Class<?>> entities = EntityScanner.doScan(); Objects.requireNonNull(entities, "Entity cant be null"); LOG.info("Urm contains entity domain total:{}", entities.size()); HibernateBundle<Configuration> hibernateBundle = new HibernateBundle<Configuration>( ImmutableList.<Class<?>>builder().add(entities.toArray(new Class<?>[0])).build(), new SessionFactoryFactory()) { @Override public PooledDataSourceFactory getDataSourceFactory(Configuration configuration) { DataSourceConfig config = (DataSourceConfig) configuration; return config.getDataSourceFactory("umebn_inventory_basic"); } }; MybatisBundle<Configuration> mybatisBundle = new MybatisBundle<Configuration>() { @Override public DataSourceFactory getDataSourceFactory(Configuration configuration) { DataSourceConfig config = (DataSourceConfig) configuration; return config.getDataSourceFactory("umebn_inventory_basic"); } }; HibernateHelper.setHibernateBundle(hibernateBundle); bootstrap.addBundle(hibernateBundle); bootstrap.addBundle(mybatisBundle); } }
3.2 MybtaisBundle实现
这里在run方法里面,会去执行 new MapperFactory().initDataSource(dbConfig),MapperFactory就加载对应的Mapper文件
public abstract class MybatisBundle<T> implements ConfiguredBundle<T> { public final void run(T configuration, Environment environment) throws Exception { DataSourceFactory dbConfig = getDataSourceFactory(configuration); new MapperFactory().initDataSource(dbConfig); } protected abstract DataSourceFactory getDataSourceFactory(T configuration); }
3.3 MapperFactory的实现
MapperFactory有个核心的方法initDataSource(),这个方法里面主要做了以下几件事
(1)通过javaAF框架扫描到的database相关配置文件解析到的DataSourceFactory ,将DataSourceFactory 中的数据库相关封装成Properties ,提供给Druid
(2)Druid实现类创建DataSource
(3)通过DataSource创建Configuration (看过mybatis代码应该知道Configuration 在mybatis是一个全局配置类,非常重要)
(4)创建sqlSessionFactory
(5)创建sqlSession 代理(具体看3.4)
(6)将加载好的sqlSession 与sqlSessionFactory加入到HK2容器中
(7)扫描Mapper注解,通过sqlSession 生成Mapper动态代理类,将代理类注入到HK2容器,在Service进行DI的时候就可以直接成功注入。
public class MapperFactory { public MapperFactory() { } private static DataSource dataSource = null; private static SqlSessionFactory sqlSessionFactory = null; private static Configuration configuration = null; public void initDataSource(DataSourceFactory dataSourceFactory) { Properties properties = parseDbProperties(dataSourceFactory); try { if (dataSource == null) { dataSource = DruidDataSourceFactory.createDataSource(properties); initSqlSessionFactory(); initMapper(); } } catch (Exception e) { log.error("Failed init dataSource", e); } } private Properties parseDbProperties(DataSourceFactory dataSourceFactory) { Properties properties = new Properties(); properties.put(DruidDataSourceFactory.PROP_DRIVERCLASSNAME, dataSourceFactory.getDriverClass()); properties.put(DruidDataSourceFactory.PROP_URL, dataSourceFactory.getUrl()); properties.put(DruidDataSourceFactory.PROP_USERNAME, dataSourceFactory.getUser()); properties.put(DruidDataSourceFactory.PROP_PASSWORD, dataSourceFactory.getPassword()); return properties; } private void initSqlSessionFactory() { TransactionFactory transactionFactory = new JdbcTransactionFactory(); Environment environment = new Environment("development", transactionFactory, dataSource); configuration = new MybatisConfiguration(environment); sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); ServiceLocator serviceLocator = ServiceLocatorFactory.getInstance().find(ServiceUtils.DW_HK2); if (serviceLocator == null) { serviceLocator = ServiceLocatorFactory.getInstance().create(ServiceUtils.DW_HK2); } ServiceLocatorUtilities.addOneConstant(serviceLocator, sqlSessionFactory, "sqlSessionFactory", SqlSessionFactory.class); ServiceLocatorUtilities.addOneConstant(serviceLocator, configuration, "configuration", Configuration.class); } private void initMapper() { Set<Class<?>> classes = mapperScanner(); ServiceLocator serviceLocator = ServiceLocatorFactory.getInstance().find(ServiceUtils.DW_HK2); classes.forEach(configuration::addMapper); SqlSession sqlSession = new SqlSessionTemplate(sqlSessionFactory); classes.forEach(clazz -> ServiceLocatorUtilities.addOneConstant(serviceLocator, sqlSession.getMapper(clazz), clazz.getName(), clazz)); } private Set<Class<?>> mapperScanner() { return ScannerUtils.scanByAnnotation(Mapper.class); }
3.4 SqlSessionTemplate的实现(进一步优化)
这里主要就是实现一个动态代理
package com.zte.ums.ume.bn.inventory.otnresmgnt.uniresmanager.mybatis; import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.executor.BatchResult; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.sql.Connection; import java.util.List; import java.util.Map; import static java.lang.reflect.Proxy.newProxyInstance; import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable; public class SqlSessionTemplate implements SqlSession { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType()); } public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) { this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionInterceptor()); } public SqlSessionFactory getSqlSessionFactory() { return this.sqlSessionFactory; } public ExecutorType getExecutorType() { return this.executorType; } @Override public <T> T selectOne(String statement) { return this.sqlSessionProxy.selectOne(statement); } @Override public <T> T selectOne(String statement, Object parameter) { return this.sqlSessionProxy.selectOne(statement, parameter); } @Override public <K, V> Map<K, V> selectMap(String statement, String mapKey) { return this.sqlSessionProxy.selectMap(statement, mapKey); } @Override public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) { return this.sqlSessionProxy.selectMap(statement, parameter, mapKey); } @Override public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds); } @Override public <T> Cursor<T> selectCursor(String statement) { return this.sqlSessionProxy.selectCursor(statement); } @Override public <T> Cursor<T> selectCursor(String statement, Object parameter) { return this.sqlSessionProxy.selectCursor(statement, parameter); } @Override public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) { return this.sqlSessionProxy.selectCursor(statement, parameter, rowBounds); } @Override public <E> List<E> selectList(String statement) { return this.sqlSessionProxy.selectList(statement); } @Override public <E> List<E> selectList(String statement, Object parameter) { return this.sqlSessionProxy.selectList(statement, parameter); } @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { return this.sqlSessionProxy.selectList(statement, parameter, rowBounds); } @Override public void select(String statement, ResultHandler handler) { this.sqlSessionProxy.select(statement, handler); } @Override public void select(String statement, Object parameter, ResultHandler handler) { this.sqlSessionProxy.select(statement, parameter, handler); } @Override public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { this.sqlSessionProxy.select(statement, parameter, rowBounds, handler); } @Override public int insert(String statement) { return this.sqlSessionProxy.insert(statement); } @Override public int insert(String statement, Object parameter) { return this.sqlSessionProxy.insert(statement, parameter); } @Override public int update(String statement) { return this.sqlSessionProxy.update(statement); } @Override public int update(String statement, Object parameter) { return this.sqlSessionProxy.update(statement, parameter); } @Override public int delete(String statement) { return this.sqlSessionProxy.delete(statement); } @Override public int delete(String statement, Object parameter) { return this.sqlSessionProxy.delete(statement, parameter); } @Override public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); } @Override public void commit() { throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); } @Override public void commit(boolean force) { throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); } @Override public void rollback() { throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); } @Override public void rollback(boolean force) { throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); } @Override public void close() { throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession"); } @Override public void clearCache() { this.sqlSessionProxy.clearCache(); } @Override public Configuration getConfiguration() { return this.sqlSessionFactory.getConfiguration(); } @Override public Connection getConnection() { return this.sqlSessionProxy.getConnection(); } @Override public List<BatchResult> flushStatements() { return this.sqlSessionProxy.flushStatements(); } /** * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the * {@code PersistenceExceptionTranslator}. */ private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = sqlSessionFactory.openSession(executorType, true); try { Object result = method.invoke(sqlSession, args); sqlSession.commit(true); return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); sqlSession.rollback(); sqlSession = null; throw unwrapped; } finally { if (sqlSession != null) { sqlSession.close(); } } } } }
四 案例演示:
(1)实体类
@TableName("demo") public class Demo { private String id; private String name; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
(2)Rest接口
@Path("/xuyu") @Contract public interface DemoApi { @POST @Path("/add") @Consumes({"application/json"}) @Produces({"application/json"}) @UnitOfWork @io.swagger.annotations.ApiOperation(value = "", notes = "新增或修改基础信息", tags = {"OtnResMgntTopo",}) @io.swagger.annotations.ApiResponses(value = { @io.swagger.annotations.ApiResponse(code = 200, message = "successful operation")}) Integer addOrUpdateBasicInfo(@ApiParam(value = "待新增或修改的基本信息", required = true) Demo demo , @Context HttpServletRequest servletRequest) throws Exception; @GET @Path("/find/{id}") @Consumes({"application/json"}) @Produces({"application/json"}) @UnitOfWork @io.swagger.annotations.ApiOperation(value = "", notes = "新增或修改基础信息", tags = {"OtnResMgntTopo"}) @io.swagger.annotations.ApiResponses(value = { @io.swagger.annotations.ApiResponse(code = 200, message = "successful operation")}) Demo find(@PathParam(value = "id") String id , @Context HttpServletRequest servletRequest) throws Exception; @POST @Path("/delete/{id}") @Consumes({"application/json"}) @Produces({"application/json"}) @UnitOfWork @io.swagger.annotations.ApiOperation(value = "", notes = "新增或修改基础信息", tags = {"OtnResMgntTopo"}) @io.swagger.annotations.ApiResponses(value = { @io.swagger.annotations.ApiResponse(code = 200, message = "successful operation")}) Integer delete(@PathParam(value = "id") String id , @Context HttpServletRequest servletRequest) throws Exception; @POST @Path("/select/{name}") @Consumes({"application/json"}) @Produces({"application/json"}) @UnitOfWork @io.swagger.annotations.ApiOperation(value = "", notes = "新增或修改基础信息", tags = {"OtnResMgntTopo"}) @io.swagger.annotations.ApiResponses(value = { @io.swagger.annotations.ApiResponse(code = 200, message = "successful operation")}) List<Demo> selectByName(@PathParam(value = "name") String name , @Context HttpServletRequest servletRequest) throws Exception; }
(3)定义Mapper接口
这里findDemoByName是通过xml映射得到的,xml文件如下,注意namspace的写法要与接口全路径保持一致
这里额外补充:mybatis-plus自身提高了一些基础的功能在BaseMapper里面,如果需要额外功能就需要自己写XML文件,以及通过注解,这三种方式
@Mapper public interface DemoMapper extends BaseMapper<Demo> { List<Demo> findDemoByName(String name); }
<?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"> <!--namespace:用来区别不同的类的名字 --> <mapper namespace="com.zte.sdotn.pg.otnresmgnt.topo.resource.DemoMapper"> <select id="findDemoByName" parameterType="String" resultType="com.zte.sdotn.pg.otnresmgnt.topo.model.Demo"> select * from demo where name like #{username} </select> </mapper>
(4)Rest接口实现
@Service public class DemoImpl implements DemoApi { @Inject private DemoMapper demoMapper; @Override public Integer addOrUpdateBasicInfo(Demo demo, HttpServletRequest servletRequest) throws Exception { return demoMapper.insert(demo); } @Override public Demo find(String id, HttpServletRequest servletRequest) throws Exception { return demoMapper.selectById(id); } @Override public Integer delete(String id, HttpServletRequest servletRequest) throws Exception { return demoMapper.deleteById(id); } @Override public List<Demo> selectByName(String name, HttpServletRequest servletRequest) throws Exception { return demoMapper.findDemoByName(name); } }
(5)接口访问与数据库
增删改查OK
六 后续改进:
(1)关于动态代理生成sqlSession那块,以及自动提交还需要改进
(2)关于mapper.xml文件的管理,需要统一起来,可以在application.yml进行配置