21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)

前言 接从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(1)
mr-service 目录结构

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
15.png mr-service这个模块简单介绍下,impl内放的是实现类,外面定义的是接口,这也是为了遵循基本的开发规范~
RedisService (redis服务类,作者没有定义接口,同学们自行判断)

package com.mrcoder.mrservice; import com.mrcoder.mrutils.redis.RedisUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; /** * redis 测试业务层 */ @Service public class RedisService { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisUtil redisUtil; //内部封装了 RedisTemplate /** * 测试存储 String 类型数据 */ public void setValue(String key, String string) { redisUtil.set(key, string); } /** * 测试读取 String 类型数据 */ public Object getValue(String key) { boolean flag = redisUtil.hasKey(key); Object rs = null; if (flag) { rs = redisUtil.get(key); } return rs; } }

StudentService
package com.mrcoder.mrservice; import com.mrcoder.mrentity.entity.master.Student; import java.util.List; public interface StudentService { public List getListByAnno(); public List getList(); public Student getById(Long id); public Integer save(Student s); public Integer update(Student s); public Integer delete(Long id); public void trans(int code); }

TeacherService (作者偷懒,就不实现Teacher服务类了)
package com.mrcoder.mrservice; public class TeacherService { }

impl/StudentServiceImpl
package com.mrcoder.mrservice.impl; import com.mrcoder.mrentity.entity.master.Student; import com.mrcoder.mrentity.entity.slave.Teacher; import com.mrcoder.mrentity.mapper.master.StudentMapper; import com.mrcoder.mrentity.mapper.slave.TeacherMapper; import com.mrcoder.mrservice.StudentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; @Autowired private TeacherMapper teacherMapper; public List getListByAnno() { return studentMapper.getListByAnno(); } public List getList() { return studentMapper.getList(); } public Student getById(Long id) { return studentMapper.getById(id); } public Integer save(Student s) { return studentMapper.insert(s); } public Integer update(Student s) { return studentMapper.update(s); } public Integer delete(Long id) { return studentMapper.delete(id); } @Transactional public void trans(int code) { Student s1 = new Student(); s1.setAge(10); s1.setGrade(10); s1.setName("s1"); studentMapper.insert(s1); Teacher t1 = new Teacher(); t1.setAge(10); t1.setName("t1"); t1.setCourse(10); teacherMapper.insert(t1); int result = 1 / code; } }

impl/TeacherServiceImpl (偷懒不写,咋地)
package com.mrcoder.mrservice.impl; public class TeacherServiceImpl { }

mr-utils 目录结构

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
16.png redis/RedisUtil
package com.mrcoder.mrutils.redis; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.CollectionUtils; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * @Description: redis 工具类 */ public class RedisUtil { private RedisTemplate redisTemplate; public void setRedisTemplate(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } /** * 指定缓存失效时间 * * @param key键 * @param time 时间(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); }/** * 判断key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } /** * 普通缓存获取 * * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * * @param key键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } }/** * 普通缓存放入并设置时间 * * @param key键 * @param value 值 * @param time时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } }/** * 递增 * * @param key键 * @param delta 要增加几(大于0) * @return */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * * @param key键 * @param delta 要减少几(小于0) * @return */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } /** * HashGet * * @param key键 不能为null * @param item 项 不能为null * @return 值 */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * * @param key 键 * @return 对应的多个键值 */ public Map hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashSet 并设置时间 * * @param key键 * @param map对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key键 * @param item项 * @param value 值 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key键 * @param item项 * @param value 值 * @param time时间(秒)注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除hash表中的值 * * @param key键 不能为null * @param item 项 可以使多个 不能为null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } /** * 判断hash表中是否有该项的值 * * @param key键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } /** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * * @param key键 * @param item 项 * @param by要增加几(大于0) * @return */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * * @param key键 * @param item 项 * @param by要减少记(小于0) * @return */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } /** * 根据key获取Set中的所有值 * * @param key 键 * @return */ public Set sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根据value从一个set中查询,是否存在 * * @param key键 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将数据放入set缓存 * * @param key键 * @param values 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 将set数据放入缓存 * * @param key键 * @param time时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) expire(key, time); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 获取set缓存的长度 * * @param key 键 * @return */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 移除值为value的 * * @param key键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } //===============================list================================= /** * 获取list缓存的内容 * * @param key键 * @param start 开始 * @param end结束0 到 -1代表所有值 * @return */ public List lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取list缓存的长度 * * @param key 键 * @return */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 通过索引 获取list中的值 * * @param key键 * @param index 索引index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 * @return */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将list放入缓存 * * @param key键 * @param value 值 * @return */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key键 * @param value 值 * @param time时间(秒) * @return */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key键 * @param value 值 * @return */ public boolean lSet(String key, List value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key键 * @param value 值 * @param time时间(秒) * @return */ public boolean lSet(String key, List value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据索引修改list中的某条数据 * * @param key键 * @param index 索引 * @param value 值 * @return */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 移除N个值为value * * @param key键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */ public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } } }
web-service 以上,我们完善了几个公共的子模块,最后,我们来完善下真正的服务子模块,也就是web-service模块
config/datasource/DataSourceConfig
package com.mrcoder.webservice.config.datasource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import javax.sql.DataSource; import java.util.Properties; /** * 数据源配置类 */ @Configuration public class DataSourceConfig { @Autowired private Environment env; @Value("${spring.datasource.type}") private String dataSourceType; /** * 配置主数据源,多数据源中必须要使用@Primary指定一个主数据源 * 其次DataSource里用的是DruidXADataSource ,而后注册到AtomikosDataSourceBean并且返回 */ @Primary @Bean(name = "masterDataSource") @DependsOn({"txManager"}) public DataSource masterDataSource() { AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); Properties prop = build(env, "spring.datasource.druid.master."); ds.setXaDataSourceClassName(dataSourceType); ds.setPoolSize(5); ds.setXaProperties(prop); return ds; } /** * 配置次数据源 * 其次DataSource里用的是DruidXADataSource ,而后注册到AtomikosDataSourceBean并且返回 */ @Bean(name = "slaveDataSource") @DependsOn({"txManager"}) public DataSource slaveDataSource() { AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); Properties prop = build(env, "spring.datasource.druid.slave."); ds.setXaDataSourceClassName(dataSourceType); ds.setPoolSize(5); ds.setXaProperties(prop); return ds; } private Properties build(Environment env, String prefix) { Properties prop = new Properties(); prop.put("name", env.getProperty(prefix + "name")); prop.put("url", env.getProperty(prefix + "url")); prop.put("username", env.getProperty(prefix + "username")); prop.put("password", env.getProperty(prefix + "password")); prop.put("driverClassName", env.getProperty(prefix + "driverClassName", "")); prop.put("filters", env.getProperty(prefix + "filters")); prop.put("maxActive", env.getProperty(prefix + "maxActive", Integer.class)); prop.put("initialSize", env.getProperty(prefix + "initialSize", Integer.class)); prop.put("maxWait", env.getProperty(prefix + "maxWait", Integer.class)); prop.put("minIdle", env.getProperty(prefix + "minIdle", Integer.class)); prop.put("timeBetweenEvictionRunsMillis", env.getProperty(prefix + "timeBetweenEvictionRunsMillis", Integer.class)); prop.put("minEvictableIdleTimeMillis", env.getProperty(prefix + "minEvictableIdleTimeMillis", Integer.class)); prop.put("validationQuery", env.getProperty(prefix + "validationQuery")); prop.put("testWhileIdle", env.getProperty(prefix + "testWhileIdle", Boolean.class)); prop.put("testOnBorrow", env.getProperty(prefix + "testOnBorrow", Boolean.class)); prop.put("testOnReturn", env.getProperty(prefix + "testOnReturn", Boolean.class)); prop.put("poolPreparedStatements", env.getProperty(prefix + "poolPreparedStatements", Boolean.class)); prop.put("maxOpenPreparedStatements", env.getProperty(prefix + "maxOpenPreparedStatements", Integer.class)); return prop; } }

config/datasource/MasterSqlSessionTemplateConfig
package com.mrcoder.webservice.config.datasource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import javax.sql.DataSource; /** * MasterSqlSessionTemplateConfig配置类 */ @Configuration //下面的sqlSessionTemplateRef 值需要和生成的SqlSessionTemplate bean name相同,如果没有指定name,那么就是方法名 @MapperScan(basePackages = {"com.mrcoder.mrentity.mapper.master"}, sqlSessionTemplateRef = "masterSqlSessionTemplate") public class MasterSqlSessionTemplateConfig { @Value("${mybatis.mapper-locations}") private String mapper_location; @Value("${mybatis.type-aliases-package}") private String type_aliases_package; @Value("${mybatis.configuration.map-underscore-to-camel-case}") private boolean mapUnderscoreToCamelCase; //将MybatisConfig类中初始化的对象注入进来 @Autowired private ConfigurationCustomizer customizer; private Logger logger = LoggerFactory.getLogger(MasterSqlSessionTemplateConfig.class); /** * 自定义sqlSessionFactory配置(因为没有用到MybatisAutoConfiguration自动配置类,需要手动配置) */ @Bean public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception { logger.info("mapper文件地址为:{}", mapper_location); //在基本的 MyBatis 中,session 工厂可以使用 SqlSessionFactoryBuilder 来创建。 //而在 MyBatis-spring 中,则使用SqlSessionFactoryBean 来替代: SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); //如果重写了 SqlSessionFactory 需要在初始化的时候手动将 mapper 地址 set到 factory 中,否则会报错: //org.apache.ibatis.binding.BindingException: Invalid bound statement (not found) bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapper_location)); //下面这个setTypeAliasesPackage无效,是mybatis集成springBoot的一个bug,暂时未能解决 bean.setTypeAliasesPackage(type_aliases_package); org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); logger.info("mybatis配置驼峰转换为:{}", mapUnderscoreToCamelCase); configuration.setMapUnderscoreToCamelCase(mapUnderscoreToCamelCase); //因为没有用mybatis-springBoot自动装配,所以需要手动将configuration装配进去,要不然自定义的map key驼峰转换不起作用 customizer.customize(configuration); bean.setConfiguration(configuration); return bean.getObject(); } /** * SqlSessionTemplate 是 SqlSession接口的实现类,是spring-mybatis中的,实现了SqlSession线程安全 */ @Bean public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory); return template; } }

config/datasource/SlaveSqlSessionTemplateConfig
package com.mrcoder.webservice.config.datasource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import javax.sql.DataSource; /** * SlaveSqlSessionTemplateConfig配置类 */ @Configuration @MapperScan(basePackages = {"com.mrcoder.mrentity.mapper.slave"}, sqlSessionTemplateRef = "slaveSqlSessionTemplate") public class SlaveSqlSessionTemplateConfig { @Value("${mybatis.mapper-locations}") private String mapper_location; @Bean public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); //如果重写了 SqlSessionFactory 需要在初始化的时候手动将 mapper 地址 set到 factory 中,否则会报错: //org.apache.ibatis.binding.BindingException: Invalid bound statement (not found) bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapper_location)); return bean.getObject(); } @Bean public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory); return template; } }

config/datasource/TransactionManagerConfig
package com.mrcoder.webservice.config.datasource; import com.atomikos.icatch.jta.UserTransactionImp; import com.atomikos.icatch.jta.UserTransactionManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.transaction.jta.JtaTransactionManager; import javax.transaction.TransactionManager; import javax.transaction.UserTransaction; /** * 多数据源事务管理器配置类 */ @Configuration public class TransactionManagerConfig { /** * 分布式事务使用JTA管理,不管有多少个数据源只要配置一个 JtaTransactionManager * * @return */ @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close") public TransactionManager atomikosTransactionManager() throws Throwable { UserTransactionManager userTransactionManager = new UserTransactionManager(); userTransactionManager.setForceShutdown(false); return userTransactionManager; } @Bean(name = "txManager") @DependsOn({ "userTransaction", "atomikosTransactionManager" }) public JtaTransactionManager transactionManager() throws Throwable { UserTransaction userTransaction = userTransaction(); TransactionManager atomikosTransactionManager = atomikosTransactionManager(); return new JtaTransactionManager(userTransaction, atomikosTransactionManager); } @Bean(name = "userTransaction") public UserTransaction userTransaction() throws Throwable { UserTransactionImp userTransactionImp = new UserTransactionImp(); userTransactionImp.setTransactionTimeout(10000); return userTransactionImp; } }

config/wrapper/CustomWrapper
package com.mrcoder.webservice.config.wrapper; import com.google.common.base.CaseFormat; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.wrapper.MapWrapper; import java.util.Map; /** * @Description: 自定义wrapper处理spring boot + mybatis返回结果为map时的key值转换为驼峰 */ public class CustomWrapper extends MapWrapper { public CustomWrapper(MetaObject metaObject, Map map) { super(metaObject, map); } @Override public String findProperty(String name, boolean useCamelCaseMapping) { if (useCamelCaseMapping) { return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name); } return name; } }

config/wrapper/MapWrapperFactory
package com.mrcoder.webservice.config.wrapper; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.wrapper.ObjectWrapper; import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; import java.util.Map; /** * 实现接口 ObjectWrapperFactory,通过包装工厂来创建自定义的wrapper */ public class MapWrapperFactory implements ObjectWrapperFactory { @Override public boolean hasWrapperFor(Object object) { return object != null && object instanceof Map; } @Override public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) { return new CustomWrapper(metaObject, (Map) object); } }

config/DruidConfig
package com.mrcoder.webservice.config; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 配置 Druid登陆配置(白名单等) */ @Configuration public class DruidConfig { @Value("${spring.druid.name}") private String name; @Value("${spring.druid.pass}") private String pass; private Logger logger = LoggerFactory.getLogger(DruidConfig.class); /** * 配置 Druid控制台 白名单、黑名单、用户名、密码等 * 访问地址ip+port/projectContextPath/druid/index.html */ @Bean public ServletRegistrationBean DruidStatViewServlet() { logger.info("servletRegistrationBean configure is starting..."); //org.springframework.boot.context.embedded.ServletRegistrationBean提供类的进行注册. ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); //添加初始化参数 servletRegistrationBean.addInitParameter("allow", "*"); servletRegistrationBean.addInitParameter("loginUsername", name); servletRegistrationBean.addInitParameter("loginPassword", pass); //是否可以重置 servletRegistrationBean.addInitParameter("resetEnable", "false"); return servletRegistrationBean; } /** * 注册一个:filterRegistrationBean */ @Bean public FilterRegistrationBean druidStatFilter2() { logger.info("filterRegistrationBean configure is starting..."); FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter()); //添加过滤规则. filterRegistrationBean.addUrlPatterns("/*"); //添加不需要忽略的格式信息. filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); return filterRegistrationBean; } }

config/MybatisConfig
package com.mrcoder.webservice.config; import com.mrcoder.webservice.config.wrapper.MapWrapperFactory; import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * mybatis配置类,将自定义的MapWrapperFactory覆盖默认的ObjectWrapperFactory */ @Configuration public class MybatisConfig { private Logger logger = LoggerFactory.getLogger(MybatisConfig.class); @Bean public ConfigurationCustomizer mybatisConfigurationCustomizer() { logger.info("initialize the ConfigurationCustomizer...."); return new ConfigurationCustomizer() { @Override public void customize(org.apache.ibatis.session.Configuration configuration) { configuration.setObjectWrapperFactory(new MapWrapperFactory()); } }; } }

config/RedisConfig
package com.mrcoder.webservice.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.mrcoder.mrutils.redis.RedisUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.*; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @redis 配置类 */ @Configuration public class RedisConfig { private Logger logger = LoggerFactory.getLogger(RedisConfig.class); @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate<>(); // 配置连接工厂 template.setConnectionFactory(factory); //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式) Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(om); // 值采用json序列化 template.setValueSerializer(jacksonSeial); //使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); // 设置hash key 和value序列化模式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(jacksonSeial); template.afterPropertiesSet(); return template; } /** * 对hash类型的数据操作 * * @param redisTemplate * @return */ @Bean public HashOperations hashOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForHash(); } /** * 对redis字符串类型数据操作 * * @param redisTemplate * @return */ @Bean public ValueOperations valueOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForValue(); } /** * 对链表类型的数据操作 * * @param redisTemplate * @return */ @Bean public ListOperations listOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForList(); } /** * 对无序集合类型的数据操作 * * @param redisTemplate * @return */ @Bean public SetOperations setOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForSet(); } /** * 对有序集合类型的数据操作 * * @param redisTemplate * @return */ @Bean public ZSetOperations zSetOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForZSet(); } /** * 使用上面初始化的RedisTemplate实例来初始化一个RedisUtil对象 * ps:@Qualifier("redisTemplate") 可以不用加,如果存在RedisTemplate实例,spring会默认注入 */ @Bean public RedisUtil redisUtil(@Qualifier("redisTemplate") RedisTemplate redisTemplate) { RedisUtil redisUtil = new RedisUtil(); redisUtil.setRedisTemplate(redisTemplate); return redisUtil; } }

config/Swagger2Config
package com.mrcoder.webservice.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /** * @Description: swagger2配置类 */ @Configuration @EnableSwagger2 public class Swagger2Config { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.mrcoder.webservice.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("SPRING-BOOT整合MYBATIS--API说明文档") .description("2019-04-26") .version("1.0.0") .license("署名-MrCoder") .build(); } }

controller/TestController
package com.mrcoder.webservice.controller; import com.mrcoder.mrentity.entity.master.Student; import com.mrcoder.mrservice.RedisService; import com.mrcoder.mrservice.StudentService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @Api(value = "https://www.it610.com/article/Test", description = "Test测试Controller") public class TestController { @Autowired private StudentService studentService; @Autowired private RedisService redisService; private Log logger = LogFactory.getLog(getClass()); @ApiOperation(value = "https://www.it610.com/article/log测试接口", notes = "测试写level日志") @RequestMapping("log") public void testAll() { logger.info("====info"); logger.warn("====warn"); logger.error("====error"); } //列表查询 @RequestMapping("getStudentList") public ResponseEntity getStudentList() { return ResponseEntity.ok(studentService.getList()); } //列表查询 @RequestMapping("getStudentListByAnno") public ResponseEntity getStudentListByAnno() { return ResponseEntity.ok(studentService.getListByAnno()); } //新增 @RequestMapping("addStudent") public int addStudent() { Student student = new Student(); student.setAge(1); student.setGrade(1); student.setName("student"); return studentService.save(student); } //更新 @RequestMapping("updateStudent/{id}") public int updateStudent(@PathVariable(name = "id") Long id) { Student student = new Student(); student.setAge(10); student.setGrade(10); student.setName("update"); student.setId(id); return studentService.update(student); } //删除 @RequestMapping("deleteStudent/{id}") public int deleteStudent(@PathVariable(name = "id") Long id) { return studentService.delete(id); } //事务 @RequestMapping("transSuccess") public void transSuccess() { //人为制造0除异常测试事务分布式事务回滚 studentService.trans(1); } //事务 @RequestMapping("transRoll") public void transRoll() { //人为制造0除异常测试事务分布式事务回滚 studentService.trans(0); } //redis string读写 @RequestMapping(value = "https://www.it610.com/redis") public String redis() { redisService.setValue("test", "test123"); return redisService.getValue("test").toString(); } }

WebServiceApplication
package com.mrcoder.webservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @ComponentScan(basePackages = {"com.mrcoder"}) public class WebServiceApplication { public static void main(String[] args) { SpringApplication.run(WebServiceApplication.class, args); } }

注意
@ComponentScan(basePackages = {"com.mrcoder"})

这个注解是用来扫描com.mrcoder下的所有类的。
核心配置,主要的数据源配置都在此
resources/application.yml
## 公共配置start ## 配置数据源相关信息 spring: ## 环境配置 profiles: active: dev ## 数据源设置 datasource: type: com.alibaba.druid.pool.xa.DruidXADataSource druid: ## 连接池配置 master: ## JDBC配置 name: master url: jdbc:mysql://192.168.145.131:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver filters: stat maxActive: 20 initialSize: 1 maxWait: 60000 minIdle: 1 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: select 'x' testWhileIdle: true testOnBorrow: true testOnReturn: true poolPreparedStatements: true maxOpenPreparedStatements: 20 slave: ## JDBC配置 name: slave url: jdbc:mysql://192.168.145.131:3306/test2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver filters: stat maxActive: 20 initialSize: 1 maxWait: 60000 minIdle: 1 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: select 'x' testWhileIdle: true testOnBorrow: true testOnReturn: true poolPreparedStatements: true maxOpenPreparedStatements: 20redis: host: 127.0.0.1# redis服务所在的地址 port: 6379 password:# redis的密码默认为空 pool: max-active: 8#连接池最大连接数(使用负值表示没有限制) max-idle: 8#连接池最大空闲数 min-idle: 1#连接池最小空闲数 max-wait: 60000#获取连接的超时等待事件 timeout: 30000#连接redis服务器的最大等待时间#druid监控页面用户名和密码 druid: name: admin pass: admin## 该配置节点为独立的节点 mybatis: mapper-locations: classpath:mapper/*/*.xml# 注意:一定要对应mapper映射xml文件的所在路径 type-aliases-package: com.mrcoder.mrentity.entity # 注意:对应实体类的路径 configuration: map-underscore-to-camel-case: true## 公共配置 end--- ## 开发环境配置 spring: profiles: dev server: port: 8080--- ## 测试环境配置 spring: profiles: test server: port: 8082--- ## 生产环境配置 spring: profiles: prod server: port: 8084

这边细心的盆友发现了

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
17.png 这边是为了区分多个环境的配置,实际项目开发时,都会区分开发/联调/测试/生产等环境,yml配置文件本身支持多环境的区分,用“---”隔开来不同环境。
公用的配置写在最上方即可,需要采用的配置用"active"关键词声明即可。

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
18.png atomikos分布式事务管理器配置
resources/jta.properties
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory com.atomikos.icatch.max_timeout=600000 com.atomikos.icatch.default_jta_timeout=600000 com.atomikos.icatch.log_base_dir =transaction-logs com.atomikos.icatch.log_base_name=springboot-mybatis com.atomikos.icatch.serial_jta_transactions=false

日志配置
resources/logback-spring.xml
logback debug %date %level [%thread] %logger{36} [%file : %line] %msg%n logs/logback_debug.log ${path}/logback_debug.log.%d{yyyy-MM-dd}-%i.zip ${maxHistory} ${maxFileSize} %date %level [%thread] %logger{36} [%file : %line] %msg%n DEBUG ACCEPT DENY logs/logback_info.log ${path}/logback_info.log.%d{yyyy-MM-dd}-%i.zip ${maxHistory} ${maxFileSize} %date %level [%thread] %logger{36} [%file : %line] %msg%n INFO ACCEPT DENY logs/logback_warn.log ${path}/logback_warn.log.%d{yyyy-MM-dd}-%i.zip ${maxHistory} ${maxFileSize} %date %level [%thread] %logger{36} [%file : %line] %msg%n WARN ACCEPT DENY logs/logback_error.log ${path}/logback_error.log.%d{yyyy-MM-dd}-%i.zip ${maxHistory} ${maxFileSize} %date %level [%thread] %logger{36} [%file : %line] %msg%n ERROR ACCEPT DENY

至此,所有的代码均已完善,大家已经可以运行

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
19.png 运行 http://localhost:8080/log
http://localhost:8080/getStudentList
http://localhost:8080/getStudentListByAnno
http://localhost:8080/addStudent
http://localhost:8080/updateStudent/1
http://localhost:8080/deleteStudent/1
http://localhost:8080/transSuccess
http://localhost:8080/transRoll
http://localhost:8080/redis
关于druid和swagger2的使用 druid监控 (具体使用请查看官方文档)
http://localhost:8080/druid 账号和密码在application.yml有配置

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
20.png
swagger2 api文档 (具体使用请查看官方文档)
http://localhost:8080/swagger-ui.html
关于swagger2 api文档的自动生成,我们只是简单的测试了一下

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
21.png
关于mybatis 代码自动生成 这个其实在前几章,作者已经介绍过了,但是这边再讲一下。
我们看mr-entity子模块的pom.xml文件,可以看到注释的build信息,只需放开注释,我们假设在你的master数据源(也就是test库)中有张person表,你想自动生成entity和mapper文件,只需修改resources/generator/masterGenerator.xml

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
22.png 然后点击IDEA右侧

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
23.png 关于打包的介绍 关于打包的几个pom.xml build块配置
先看父模块pom.xml中的

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
24.png
这个是跑测试用例JUnit的配置
再看web-service子模块的pom.xml配置

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
25.png
重要的掉已经在截图中标红了,大家可以重点看下~
最后,我们再看mr-entity子模块pom.xml中的build信息

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
26.png
可以看到,这边是注释的,为啥注释呢?因为像mr-entity,mr-service,mr-utils等模块无需打包,我们最终需要打包的只有真正的应用web-service模块。也就是说只有需要打包的模块才能有build配置信息。
最后,打包命令我们只需在IDEA右侧点击

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
27.png 注意,作者打包时操作的是root项目,也就是父项目。操作父项目执行的操作会对所有的子模块执行,若同学们只想打包某个子项目只需对对应的子模块下执行操作即可~
强调一下重点,先clean,再package!
执行完成后,我们看IDEA的console口出现

21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
28.png 此时,我们发现,各个模块下都会出现一个target文件夹,且里面会有对应的jar包,我们找到web-service服务模块的jar包(为什么找这个,因为只有它才是真正提供服务的模块,才有入口启动类,通俗点,只有它能运行,不懂的可以再回头看看文章。)
我们再cmd下执行 jar -jar xxxx.jar包 即可运行该服务!linux下同理!注意,必须按照JDK环境,这里就不讲如何安装配置JDK了。
总结 好了,至此,一个可生产的springboot项目结构已经搭建完毕,该结构可以直接拿去投产,从结构的构思到demo的编写,作者花了几天的时间,还请用的上的同学关注下作者的公众号,同时对github点个赞~
项目地址 【21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)】https://github.com/MrCoderStack/SpringBootFrame
请关注我的订阅号 21.1.|21.1. 从零开始学springboot-搭建一个可以上线的项目结构-多模块篇(2)
文章图片
订阅号.png

    推荐阅读