SpringBoot + Mybatis + RESTful(Jersey)

原创
2017/04/13 23:13
阅读数 1.5W

概述

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。Spring Boot 内置了大量的常用习惯性的配置,使你无需手动配置,使用 Spring Boot 你可以不用或者只需要很少的配置就可以快速的构建你自己的项目。

MyBatis 是时下非常流行的支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。

Jersey RESTful 框架是开源的RESTful框架, 实现了 JAX-RS 规范。它扩展了JAX-RS 参考实现, 提供了更多的特性和工具, 可以进一步地简化 RESTful service 和 client 开发。

SpringBoot + Mybatis + RESTful(Jersey, RestEasy)可以说是时下主流的服务端WEB开发的黄金搭配,本文主要介绍如何在生产项目开发中正确的集成 Spring Boot + MyBatis + Jersey。

整合案例介绍

在一些大型的网站或者应用中,数据库服务器单点难以支撑大的访问压力,升级服务器性能成本又太高,所以通用的做法是水平扩展,本案例采用多数据源的配置方式(主从配置)即主库主要用来处理增,删,改操作,从库用来只读操作,也就是常说的读写分离。

接下来将主要介绍多数据源的配置以及Mybatis的基本仓储实现,由于篇幅原因这里不一一贴出所有的代码,具体完整案例代码可以看这里https://github.com/AIFEINIK/SpringBoot-Learn/tree/master/spring-boot-mybatis

案例多数据源配置详细解释

1、基本目录如下,datasource包目录下主要存放多数据源的配置类,db根目录下的类主要是Mybatis操作的基本仓储实现。

2、DsConfig类是主从库的公共数据库连接配置

public class DsConfig {

   private String name;

   // 可以在连接池中同时被分配的有效连接数的最大值,如设置为负数,则不限制
   private int maxTotal = 8;

   // 可以在连接池中保持空闲的最大连接数,超出设置值之外的空闲连接将被回收,如设置为负数,则不限制
   private int maxIdle = 8;

   // 可以在连接池中保持空闲的最小连接数,超出设置值之外的空闲连接将被创建,如设置为0,则不创建
   private int minIdle = 0;

   // 当这个连接池被启动时初始化的创建的连接个数
   private int initialSize = 0;

   // 在连接池返回连接给调用者前用来进行连接校验的查询sql。如果指定,则这个查询必须是一个至少返回一行数据的SQL
   // SELECT语句。如果没有指定,则连接将通过调用isValid() 方法进行校验。
   private String validationQuery;

   // 指明对象是否需要通过对象驱逐者进行校验(如果有的话),假如一个对象校验失败,则对象将被从连接池中释放。
   private boolean testWhileIdle = false;

   // 指明在从连接池中租借对象时是否要进行校验,如果对象校验失败,则对象将从连接池释放,然后我们将尝试租借另一个
   private boolean testOnBorrow = true;// false?

   // 指明在将对象归还给连接池前是否需要校验。
   private boolean testOnReturn = false;

   // (如果没有可用连接)连接池在抛出异常前等待的一个连接被归还的最大毫秒数,设置为-1则等待时间不确定
   private long maxWaitMillis = -1;

   // 空闲对象驱逐线程运行时的休眠毫秒数,如果设置为非正数,则不运行空闲对象驱逐线程
   private long timeBetweenEvictionRunsMillis = -1;

   // 在每个空闲对象驱逐线程运行过程中中进行检查的对象个数。(如果有的话)
   private int numTestsPerEvictionRun = 3;

   // 符合对象驱逐对象驱逐条件的对象在池中最小空闲毫秒总数(如果有的话)
   private long minEvictableIdleTimeMillis = 1000 * 60 * 30;

   set{...}
   get{...}

}

3、MasterDsConfig类是主库的连接配置

@Configuration
@ConfigurationProperties("datasource.master")
public class MasterDsConfig extends DsConfig {
    //这里可以写master库自己的属性
}

注:@ConfigurationProperties 注解可以用来读取配置文件中的值然后注入到类属性中

4、MasterDataSourceConfig类为主库数据源配置

@Configuration
public class MasterDataSourceConfig {

    @Value("${mybatis.config-location}")
    private String mybatisConfigLocation;

    @Autowired
    private MasterDsConfig config;

    @Primary
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "datasource.master")
    public DataSource masterDataSource() {
        BasicDataSource ds = (BasicDataSource) DataSourceBuilder.create().type(BasicDataSource.class).build();
        ds.setMaxTotal(config.getMaxTotal());
        ds.setMaxIdle(config.getMaxIdle());
        ds.setMinIdle(config.getMinIdle());
        ds.setInitialSize(config.getInitialSize());
        ds.setValidationQuery(config.getValidationQuery());
        ds.setTestWhileIdle(config.isTestWhileIdle());
        ds.setTestOnBorrow(config.isTestOnBorrow());
        ds.setTestOnReturn(config.isTestOnReturn());
        ds.setMaxWaitMillis(config.getMaxWaitMillis());
        // ds.setTimeBetweenEvictionRunsMillis(config.getTimeBetweenEvictionRunsMillis());
        // ds.setNumTestsPerEvictionRun(config.getNumTestsPerEvictionRun());
        // ds.setMinEvictableIdleTimeMillis(config.getMinEvictableIdleTimeMillis());
        ds.setDefaultAutoCommit(false);
        return ds;
    }

    @Bean(name = "masterTransactionManager")
    public DataSourceTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "masterSqlSessionFactory")
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource)
            throws Exception {
        String path = mybatisConfigLocation.replace("classpath:", "/");
        ClassPathResource resource = new ClassPathResource(path);
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setConfigLocation(resource);
        return factory.getObject();
    }

    @Bean(name = "masterSqlSessionTemplate")
    public SqlSessionTemplate masterSqlSessionTemplate(
            @Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

 

注:1、@Primary 注解表示当有多个相同的Bean时,优先使用该Bean。

       2、@ConfigurationProperties 注解到@Bean注解的方法上表示将配置文件中前缀为datasource.master的值注入到该Bean对应的属性字段上。如这里将application.yml配置文件中前缀为datasource.master的值注入到了BasicDataSource实例的对应属性字段中。

从库数据源配置与主库数据源配置基本相同,这里不再介绍。

Mybatis数据库操作的基本仓储实现

1、EntityRepository 仓储访问接口, 提供通用仓储方法

public interface EntityRepository<T extends Entity> {

   // 添加一个实体
   public T insert(T t) throws RepositoryException;

   // gen一个实体
   public int update(T t) throws RepositoryException;

   // 添加一个实体(update and insert)
   public T save(T t) throws RepositoryException;

   // 更新一个实体
   public int updateSpecify(T t) throws RepositoryException;

   // 移除一个实体
   public int delete(T t) throws RepositoryException;

   // 根据实体ID,删除实体
   public int deleteById(Integer id) throws RepositoryException;

   // 根据实体ID,查找实体
   public T getById(Integer id);

   // 查询符合查询参数的实体结果集数量
   public int findResultCount(QueryParameters param);

   // 查询符合查询参数的实体结果集
   public List<T> findResults(QueryParameters param);

}

 2、MybatisEntityRepository基于MyBatis的基本仓储实现

public abstract class MybatisEntityRepository<T extends Entity> implements EntityRepository<T> {

   protected final static Logger logger = LoggerFactory.getLogger(MybatisEntityRepository.class);

   protected SqlSessionFactory sqlSessionFactory;

   protected abstract void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory);

   //自定义Mybatis的sql配置文件中的访问命名空间
   protected abstract String nameSpaceForSqlId();

   //自定义Mybatis的sql配置文件中的SqlId
   protected String fullSqlId(String sqlId) {
      return nameSpaceForSqlId() + "." + sqlId;
   }

   @Override
   public T insert(T t) throws RepositoryException {
      try (SqlSession session = sqlSessionFactory.openSession()) {
         session.insert(fullSqlId("insert"), t);
         session.commit(true);
      } catch (Exception e) {
         logger.error("Insert Entity failed: ", e);
         throw new RepositoryException(e);
      }
      return t;
   }

   @Override
   public int update(T t) throws RepositoryException {
      int ret = 0;
      try (SqlSession session = sqlSessionFactory.openSession()) {
         ret = session.update(fullSqlId("update"), t);
         session.commit(true);
      } catch (Exception e) {
         logger.error("Update Entity failed: ", e);
         throw new RepositoryException(e);
      }
      return ret;
   }

   @Override
   public T save(T t) throws RepositoryException {
      try (SqlSession session = sqlSessionFactory.openSession()) {
         int ret = 0;
         if (t.getId() != null) {
            ret = session.update(fullSqlId("update"), t);
         }
         if (ret == 0) {
            ret = session.insert(fullSqlId("insert"), t);
         }
         session.commit(true);
      } catch (Exception e) {
         logger.error("Save/Update Entity failed: ", e);
         throw new RepositoryException(e);
      }
      return t;
   }

   @Override
   public int updateSpecify(T t) throws RepositoryException {
      int count = 0;
      try (SqlSession session = sqlSessionFactory.openSession()) {
         count = session.update(fullSqlId("updateSpecify"), t);
         session.commit(true);
      } catch (Exception e) {
         logger.error("UpdateSpecify Entity failed: ", e);
         throw new RepositoryException(e);
      }
      return count;
   }

   @Override
   public int deleteById(Integer id) throws RepositoryException {
      int count = 0;
      try (SqlSession session = sqlSessionFactory.openSession()) {
         count = session.delete(fullSqlId("deleteById"), id);
         session.commit(true);
      } catch (Exception e) {
         logger.error("Remove Entity failed: ", e);
         throw new RepositoryException(e);
      }
      return count;
   }

   @Override
   public int delete(T t) throws RepositoryException {
      int count = 0;
      try (SqlSession session = sqlSessionFactory.openSession()) {
         count = session.delete(fullSqlId("delete"), t);
         session.commit(true);
      } catch (Exception e) {
         logger.error("Remove Entity failed: ", e);
         throw new RepositoryException(e);
      }
      return count;
   }

   @Override
   public T getById(Integer id) {
      try (SqlSession session = sqlSessionFactory.openSession()) {
         return session.selectOne(fullSqlId("getById"), id);
      }
   }

   @Override
   public int findResultCount(QueryParameters params) {
      if (params == null) { // 纠错
         params = new QueryParameters();
      }
      try (SqlSession session = sqlSessionFactory.openSession()) {
         return session.selectOne(fullSqlId("findResultCount"), params);
      }
   }

   @Override
   public List<T> findResults(QueryParameters params) {
      if (params == null) { // 纠错
         params = new QueryParameters();
      }
      try (SqlSession session = sqlSessionFactory.openSession()) {
         return session.selectList(fullSqlId("findResults"), params);
      }
   }

}

3、MasterMybatisEntityRepository 主库的Mybatis仓储

public abstract class MasterMybatisEntityRepository<T extends Entity> extends MybatisEntityRepository<T> {

    @Autowired
    @Qualifier("masterSqlSessionFactory")
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
}

4、SlaveMybatisEntityRepository 从库的Mybatis仓储

public abstract class SlaveMybatisEntityRepository<T extends Entity> extends MybatisEntityRepository<T> {

    @Autowired
    @Qualifier("slaveSqlSessionFactory")
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
}

5、最后就可以编写repo类来通过Myabtis基础仓储访问数据库了

@Repository
public class PersonRepo extends MasterMybatisEntityRepository<Person> {

    @Override
    protected String nameSpaceForSqlId() {
        //这里配置的namespace与PersonMapper.xml中的namespace要保持一致
        return "com.os.china.mapper.PersonMapper";
    }

    /**
     * Mybatis基本仓储中没有的可以自己写
     * @param userName
     * @return
     */
    public Person getPersonByName(String userName) {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            return session.selectOne(fullSqlId("getPersonByName"), userName);
        }
    }
}

集成 RESTful 框架 Jersey

1、Maven导入Jersey包

<!-- 使用jersey RESTful 框架 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jersey</artifactId>
    <version>${boot.version}</version>
</dependency>

2、编写Jersey配置类

@Configuration
public class JerseyConfig extends ResourceConfig {

   /**
    * 扫描com.os.china包,使其识别JAX-RS注解
    */
   public JerseyConfig() {
      packages("com.os.china");
   }

}

RESTful风格的资源请求处理类编写

@Component
@Path("personMgr")
public class PersonMgrResource {

    @Autowired
    private PersonService personService;

    @GET
    @Path("getPersonById")
    @Produces(MediaType.APPLICATION_JSON)
    public JsonResp getPersonById(@QueryParam("id") Integer id) {
        Person person = personService.getPersonById(id);
        return JsonResp.success(person);
    }

    @GET
    @Path("getPersonByName")
    @Produces(MediaType.APPLICATION_JSON)
    public JsonResp getPersonByName(@QueryParam("userName") String userName) {
        Person person = personService.getPersonByName(userName);
        return JsonResp.success(person);
    }

    /**
     * 分页查询用户信息
     * @param req 封装分页参数
     * @return
     */
    @POST
    @Path("getPersons")
    @Produces(MediaType.APPLICATION_JSON)
    public PageResp getAllPersons(@Valid PageReq req) {
        QueryParameters params = new QueryParameters(req);
        int total = personService.getPersonCount(params);
        if (total == 0) {
            return PageResp.emptyResult();
        }

        PageInfo page = new PageInfo(req.getPage(), req.getCount(), total);
        params.setPage(page);
        List<Person> persons = personService.getPersons(params);
        return PageResp.success(total, persons);
    }

}

测试

在浏览器中使用postman插件来测试

总结

本文主要介绍了 SpringBoot 集成 Mybatis 多数据源的配置,以及如何集成 RESTful 框架 Jersey,最后通过简单测试来验证整个流程是否流转成功,完整代码可在GitHub中查看,如有任何疑问,请赐教!

展开阅读全文
打赏
11
179 收藏
分享
加载中
FEINIK博主
该评论暂时无法显示,详情咨询 QQ 群:912889742
我想问下你都是在MybatisEntityRepository里控制事务的,如果我想写个service方法并通过@ Transactional让spring管理事务,我在MybatisEntityRepository里该怎么写?怎么获取当前sqlsession?
2018/06/20 23:17
回复
举报
~~
2017/07/13 23:35
回复
举报
非常有参考价值
2017/04/27 21:30
回复
举报
FEINIK博主

引用来自“铂金小虫”的评论

一般意义上的主从会有中间件自动处理sql的路由。像你说的写主读从。但是看了你代码更像是普通的多数据源分库。比如Person类,你就放到了主中(PersonRepo继承Master相关)。这样person不会读从吧。
你好,这里PersonRepo类只是一个简单的测试,一般在项目中读写分离也是针对数据量特别大的而且业务操作特别频繁的场景,例如短信业务,那么这里可以分装一层业务分别实现不同的数据源来单独分开处理该业务数据的读写。😊
2017/04/15 17:35
回复
举报
一般意义上的主从会有中间件自动处理sql的路由。像你说的写主读从。但是看了你代码更像是普通的多数据源分库。比如Person类,你就放到了主中(PersonRepo继承Master相关)。这样person不会读从吧。
2017/04/15 13:26
回复
举报
用Spring MVC的RestController更简单。
2017/04/15 10:32
回复
举报
谢谢分享
2017/04/15 09:55
回复
举报
好好啊
2017/04/14 22:30
回复
举报
FEINIK博主

引用来自“Joyzhou”的评论

没看出Jersey的意义?
使用RESTful框架其实简化了web服务的开发,使其更易被理解,这里只是简单的介绍,更多细节可以查看官网https://jersey.java.net/😄
2017/04/14 18:03
回复
举报
更多评论
打赏
14 评论
179 收藏
11
分享
返回顶部
顶部