从零自学SpringBoot2
本人从零开始学习SpringBoot2,会将一些学习过程展现在这里。包括如何整合常用的Java企业开发框架、期间遇到的一些坑等。大部分资料在互联网上都寻的到,我只是做了一个综合整理,做了一些自己觉得合理的改动和增加一些注释。
  • SpringBoot2从零开始(一)——项目启动   

    Maven中groupId、artifactId、version+ groupId - the id of the project's group.+ artifactId - the id of the artifact (project)+ version - the version of the artifact under the specified groupA POM requires that its groupId, artifactId, and version be configured. These three values form the project's fully qualified artifact name. This is in the form of ::. As for the example above, its fully qualified artifact name is "com.mycompany.app:my-app:1".--- 开始SpringBoot 2 一、初始化一个web项目访问 [start.spring][startSpring] ,创建快速创建一个SpringBoot2的web项目。Dependencies选择一个Web模块即可,然后点击下方Generate Project alt+回撤按钮。解压下载的zip包,导入的IDE中,我使用是eclipse。在你 的groupId.artifactId目录下找到Application文件,以main方法运行即可快速即启动一个web项目。springboot官方建议的根目录(groupId.artifactId)下,主要有三个package:+ domain:主要存放数据库访问相关对象以及映射类。+ service:业务层代码。+ controller:控制层代码,负责页面访问。当然,如果是多Module项目,另当别论。在controller目录下创建一个FirstController.java文件,增加一个示例接口:javapackage com.wj.springboot2demo.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class FirstController { / @RequestParam 注解有四个参数,均为非必填: defaultValue 指定了参数的默认值 required 参数是否必传 value和name参数互为别名,可以同时指定,但是值必须一致,否则报错: / @RequestMapping("/springboot2/index") public String index(@RequestParam(defaultValue "defaultV", value "value", name"value1") String v) { return "first springboot 2 controller : " + v; }} 当@RequestParam的value和name同时指定,但是值不同,将会报错: org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.core.annotation.AnnotationConfigurationException: In annotation [org.springframework.web.bind.annotation.RequestParam] declared on public java.lang.String com.wj.springboot2demo.controller.FirstController.index(java.lang.String) and synthesized from [@org.springframework.web.bind.annotation.RequestParam(namevalue1, valuevalue, defaultValuedefaultV, requiredtrue)], attribute 'name' and its alias 'value' are present with values of [value1] and [value], but only one is permitted.以main方法执行Application之后,通过浏览器访问http://localhost:8080/springboot2/index,启动成功返回first springboot 2 controller。--- 二、单元测试javaimport org.junit.Before;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.http.MediaType;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.test.web.servlet.MockMvc;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.;import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.;import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.;import org.springframework.test.web.servlet.setup.MockMvcBuilders;@RunWith(SpringRunner.class)@SpringBootTestpublic class FirstControllerTest { //Main entry point for server-side Spring MVC test support. //mvc接口提供方,进行单元测试的降级实现入口 private MockMvc mvc; @Before public void setUp() { //用来对若干个Controller类创建实例,来模拟降级(mock)实现 mvc MockMvcBuilders.standaloneSetup(new FirstController()).build(); } @Test public void testindex() throws Exception { mvc.perform( get("/springboot2/index?value{0}", "TESTVALUE") .param("value", "getValueParameter") .param("value", "getValueParameter1") .accept(MediaType.APPLICATIONJSONUTF8) ) .andExpect(status().isOk()) .andDo(print()) .andReturn(); }} 说明1. MockMvcRequestBuilders、MockMvcResultHandlers、MockMvcResultMatchers为静态导入方式,所以直接调用了。1. 关于mvc参数传递,上面同时用了两种方式占位符传递和键值对传递,两者会同时生效,多个值通过因为逗号隔开。 shell MockHttpServletResponse: Status 200 Error message null Headers {Content-Type[application/json;charsetUTF-8], Content-Length[79]} Content type application/json;charsetUTF-8 Body first springboot 2 controller : TESTVALUE,getValueParameter,getValueParameter1 Forwarded URL null Redirected URL null 2. get为重载方法,提供了两种签名调用:public static MockHttpServletRequestBuilder get(String urlTemplate, Object... uriVars)和public static MockHttpServletRequestBuilder get(URI uri)。例子中使用了第一种签名方式,uriVars是可变参数。 + urlTemplate如果没有使用占位符,则uriVars不生效。 + /springboot2/index?value{1}&value2{0}占位符花括号中必须有内容(数字、字母等其他字符皆可,建议用递增数字或者有具体含义的描述字段,方便异常检查)否则无效,占位符中的内容没有实际意义,仅仅作为指示信息。 + 如果有效占位符数量大于后面动态参数个数,则会报错:java.lang.IllegalArgumentException: Not enough variable values available to expand '2'。 + .param("value", "getValueParameter")方法也可以设置参数,就是简单的key、value形式,如果出现同名的参数设置,会通过英文逗号分隔形成新的参数传给接口。--- 三、配置mySQL + myBatis修改application.yml配置文件内容,默认是空的。shellserver : port : 8081 spring : datasource : name : mysqltest type : com.alibaba.druid.pool.DruidDataSource druid连接池配置 druid : filters : stat driver-class-name: com.mysql.jdbc.Driver 基本属性 url: jdbc:mysql://192.168.50.42:3306/searchmanager?useUnicodetrue&characterEncodingUTF-8&allowMultiQueriestrue username: test password: test 配置初始化大小/最小/最大 initial-size: 1 min-idle: 1 max-active: 20 获取连接等待超时时间 max-wait: 60000 间隔多久进行一次检测,检测需要关闭的空闲连接 time-between-eviction-runs-millis: 60000 一个连接在池中最小生存的时间 min-evictable-idle-time-millis: 300000 validation-query: SELECT 'x' test-while-idle: true test-on-borrow: false test-on-return: false 打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false pool-prepared-statements: false max-pool-prepared-statement-per-connection-size: 20 mybatis扫描配置 mybatis: mapper-locations: classpath:mapper/.xml 这个配置myBatis的xml文件中用的java类的别名的根目录,那么xml中无需使用完整类型访问对象,只要使用默认的类名即可 aa.bb.cc.Bean.java,默认使用Bean作为别名,那么可以直接在parameterType或者resultType中使用Bean,而不是aa.bb.cc.Bean type-aliases-package: com.wj.springboot2demo.domain 需要在启动文件com.wj.springboot2demo.Springboot2demoApplication加个注解@MapperScan(basePackages "com.wj.springboot2demo.domain"),然后main方法启动。java@SpringBootApplication//myBatis的mapper扫描路径@MapperScan(basePackages "com.wj.springboot2demo.domain")public class Springboot2demoApplication { public static void main(String[] args) { SpringApplication.run(Springboot2demoApplication.class, args); }}[startSpring]: https://start.spring.io/![图片](https://oomabc.com/staticsrc/img/201809/17/153717666284922e8826e7ddf454e818b96d534bdf7b8.jpg)

    SpringBoot   Web   MyBatis   2019-04-30 浏览(1733) 有用(0) 阅读原文>> [原创]
  • SpringBoot2从零开始(二)——多数据源配置   

    零、前言多数据源配置,一直是大部分企业级开发所要面对的,即使微服务化概念提出来已经有一点时间了。微服务边界定义的好坏,直接影响系统的真实复杂度。所以即使用了提倡微服务的SpringBoot,即使SpringBoot默认约定配置是单数据源的,多数据源整合还是有其存在的必要的。当然,直接将非SpringBoot项目中多数据源配置的配置文件挪过来,稍作修改亦可实现。但是,既然使用了SpringBoot,如果依旧用那种方式,如何体现使用SpringBoot的“优越感”?至少要通过使用SpringBoot提供的诸多便利方式来实现多数据源,方可自称是SpringBoot的脑残粉,啊不,是倡导者,你说是不?---在本章开始之前,有必要了解下MyBatis在SpringBoot下大致的工作过程是怎样的。 一、MyBatis相关类解释 \\\\Mapper或者\\\\Dao+ 这是一个接口类,描述了某个数据访问的相关接口,例如:userMapper是一个tbuser表的数据访问接口。+ com.wj.domain.mapper.UserMapper是这个接口的完整类路径,这里叫做mapperInterface。+ 接口中定义了一些方法接口:int countByExample(UserExpample example)、int deleteByExample(UserExpample example)等等,其中countByExample叫做方法名,它与mapperInterface一同组成了这个方法在某个连接配置中的唯一标示。 org.apache.ibatis.mapping.MappedStatement+ 它表示了Mybatis框架中,XML文件对于sql语句节点的描述信息,包括<select /、<update /、<insert /。+ 在初始化阶段,框架会将XML配置内容转为一个个MappedStatement对象实例。+ 在XML中,mapper.namespace.id可以定位到唯一的一条SQL内容,这就是MappedStatement。所以,mapper.namespace就是前面提到的mapperInterface。同样,在XML中通过<include refid"mapper.namespace.id" /可以使用其它XML中的MappedStatement内容。 org.apache.ibatis.binding.MapperProxy它是userMapper的一个代理类,有三个主属性:+ SqlSession sqlSession:The primary Java interface for working with MyBatis. Through this interface you can execute commands, get mappers and manage transactions.+ Class mapperInterface:被代理接口的信息,比如interface com.wj.springboot2demo.domain.dao.TbSearchManagerUserMapper+ Map methodCache:MapperMethod的缓存,是一个线程安全的Map——ConcurrentHashMap,保存了每个mapper接口对应实现的sql命令和方法签名。 org.apache.ibatis.binding.MapperMethod+ MapperMethod内部维护了两个final属性,都是MapperMethod内部类。+ SqlCommand command:它有两个字段,name标识了userMapper.countByExample方法在MappedStatement配置内容中的唯一ID。type指的是SQL执行类型。+ MethodSignature method:方法签名信息。具体如下:java/ 方法详细签名信息/public static class MethodSignature { //返回值是否是VOID private final boolean returnsVoid; //是否返回多行结果 private final boolean returnsMany; //返回值是否是MAP private final boolean returnsMap; //是否返回可枚举游标 private final boolean returnsCursor; //返回值类型 private final Class returnType; //mapKey private final String mapKey; //resultHandler类型参数的位置 private final Integer resultHandlerIndex; //rowBound类型参数的位置 private final Integer rowBoundsIndex; ...} org.apache.ibatis.session.SqlSession The primary Java interface for working with MyBatis. Through this interface you can execute commands, get mappers and manage transactions. MyBatis最核心的一个接口类,通过它,可以实现SQL执行、事务管理等工作。不过,在MapperProxy中注入的SqlSession对象是org.mybatis.spring.SqlSessionTemplate,SqlSessionTemplate并没有自身对于接口org.apache.ibatis.session.SqlSession的逻辑实现,只是在内部代理了一个SqlSession的真正实现类——org.apache.ibatis.session.defaults.DefaultSqlSession,真正的Command执行逻辑都在这个类里面实现。 SqlSessionTemplate 1. 是Spring管理的,且线程安全的。 2. Spring事务管理模块维护了SqlSession在一整个连续的操作中的生命周期,包括SqlSession的创建、关闭、事务的提交、回滚等。 3. 它通过工厂方法SqlSessionFactory来创建自己所代理的SqlSession对象。 4. 由于它是线程安全的,所以它的一个实例对象可以被所有相关DAO或者Mapper共用。--- 二、初始化一个MapperMethod对象实例在Service层使用的数据库查询接口Dao或者Mapper,是一个Interface,并没有真正的实现类。例如我们使用的一个用户数量统计的Mapper类tbSearchManagerUserMapper,Spring在具有依赖关系的地方给它注入的是一个代理类的实现org.apache.ibatis.binding.MapperProxy。当执行tbSearchManagerUserMapper.countByExample(example)时,代码会进入代理对象的invoke方法MapperProxy.invoke(Object proxy, Method method, Object[] args) throws Throwable。java @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //method.getDeclaringClass()通过Method对象获得所属类的class信息 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); //判断是否是默认方法 //需要了解的内容:Method.isDefault,Method.getModifiers,以及Modifier } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } //创建一个method对应的MapperMethod对象 final MapperMethod mapperMethod cachedMapperMethod(method); //执行查询 //sqlSession 是一个org.apache.ibatis.session.SqlSession的实现类对象 return mapperMethod.execute(sqlSession, args); } //根据Method创建一个MapperMethod实例,有一层缓存 private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod methodCache.get(method); if (mapperMethod null) { //根据被代理接口的Class信息、对应方法以及连接配置,创建一个MapperMethod实例对象 mapperMethod new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); //保存到缓存中 methodCache.put(method, mapperMethod); } return mapperMethod; }javapublic static class SqlCommand { //xml标签的id,com.wj.springboot2demo.domain.dao.TbSearchManagerUserMapper.countByExample private final String name; //insert update delete select的具体类型;在执行execute时会有用 private final SqlCommandType type; //根据数据库配置信息、Mapper的接口类以及对应方法信息,构造一个SqlCommand对象 public SqlCommand(Configuration configuration, Class mapperInterface, Method method) { final String methodName method.getName(); final Class declaringClass method.getDeclaringClass(); //根据mapperInterface和methodName共同组成MappedStatement的识别ID,获取一个MappedStatement对象 MappedStatement ms resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms null) { if (method.getAnnotation(Flush.class) ! null) { name null; type SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { name ms.getId(); type ms.getSqlCommandType(); if (type SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } } private MappedStatement resolveMappedStatement(Class mapperInterface, String methodName, Class declaringClass, Configuration configuration) { //生成MappedStatement标识ID String statementId mapperInterface.getName() + "." + methodName; //如果之前已经加载, 则返回 if (configuration.hasStatement(statementId)) { return configuration.getMappedStatement(statementId); } else if (mapperInterface.equals(declaringClass)) { return null; } //根据mapperInterface实现的接口,递归调用,获取MappedStatement for (Class superInterface : mapperInterface.getInterfaces()) { if (declaringClass.isAssignableFrom(superInterface)) { MappedStatement ms resolveMappedStatement(superInterface, methodName, declaringClass, configuration); if (ms ! null) { return ms; } } } return null; } ...}--- 三、执行查询动作前面看到,在MapperProxy.invoke方法的最后一行就是执行查询动作mapperMethod.execute(sqlSession, args);,并返回结果。javapublic Object execute(SqlSession sqlSession, Object[] args) { Object result; //MapperMethod中维护的一个SqlCommand对象,其中的type属性描述的就是本次sql的动作类型SqlCommandType switch (command.getType()) { case INSERT: {//插入 Object param method.convertArgsToSqlCommandParam(args); result rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: {//更新 Object param method.convertArgsToSqlCommandParam(args); result rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: {//删除 Object param method.convertArgsToSqlCommandParam(args); result rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT://查询 //如果是查询,需要根据MethodSignature类型的属性method维护的内容进行进一步选择执行方法 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result null; } else if (method.returnsMany()) { result executeForMany(sqlSession, args); } else if (method.returnsMap()) { result executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result executeForCursor(sqlSession, args); } else { Object param method.convertArgsToSqlCommandParam(args); result sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result;}--- 四、扩展 SpringBoot + Mybatis 多数据源SpringBoot在单数据源的情况下,会自动扫描application配置文件,而且自动将DataSource与mapper.xml目录进行关联,前一张也介绍过基于连接池druid的配置。但是如果多数据源的配置,SpringBoot默认无法将不同的DataSource与不同的mapper.xml目录进行关联。 事实上,SpringBoot 与 SpringCloud一样,都倡积极导微服务的概念和实践。微服务可以使不同的团队专注于更小范围的工作职责、使用独立的技术、更安全更频繁地部署。通常情况下,服务边界定义合理的微服务只会访问单一数据源。我猜测这就是SpringBoot在默认情况下,经过简单配置就可以实现数据源整合的原因,而多数据源的配置就需要用户自己去扩展实现。前面已经说过,MyBatis对应Mapper进行查询的时候,实际数据库连接对象是SqlSessionTemplate,它由SqlSessionFactory对象创建而来,实际上SqlSessionTemplate内部维护了一个SqlSessionFactory实例。那么,我们可以自己写一个类,将创建DataSource、创建SqlSessionFactory和创建SqlSessionTemplate三步动作合并,同时将创建的SqlSessionTemplate与mapper.xml目录进行关联。 1、创建多数据源公用配置类上一章[《SpringBoot 2 从零开始(一)——项目启动 》][lastSpringBoot]提到过,在启动项目类上加了一个注解@MapperScan(basePackages "com.wj.springboot2demo.domain")。java@MapperScan(basePackages "com.wj.springboot2demo.domain")public class Springboot2demoApplication { public static void main(String[] args) { SpringApplication.run(Springboot2demoApplication.class, args); }}其实,这个注解就是可以将SqlSessionTemplate与mapper.xml目录进行关联。所以,我们自定义配置类,只要能够根据配置文件创建SqlSessionTemplate就行了。首先,我们定义一个公共配置类,这是一个好习惯,即使这类中没有任何内容。当然,我这个类是最终完成版本,所以里面并不是空的。javapublic class AbstractDataSourceConfiger { //与某个SqlSessionTemplate关联的XML目录 //在配置文件中的位置和DataSource参数位置属于同一级别,参数名是:mapper-locations private String mapperLocations; //MyBatis的xml文件中使用的类的别名,位置与mapper-locations统一级别,参数名:type-aliases-package private String typeAliasesPackage; //根据dataSource创建一个SqlSessionFactory对象 protected SqlSessionFactory createSessionFactory( DataSource dataSource) { SqlSessionFactoryBean bean new SqlSessionFactoryBean(); bean.setDataSource(dataSource); // 添加XML目录 ResourcePatternResolver resolver new PathMatchingResourcePatternResolver(); try { bean.setMapperLocations(resolver.getResources(getMapperLocations())); bean.setTypeAliasesPackage(getTypeAliasesPackage()); return bean.getObject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } public String getTypeAliasesPackage() { return typeAliasesPackage; } public void setTypeAliasesPackage(String typeAliasesPackage) { this.typeAliasesPackage typeAliasesPackage; } public String getMapperLocations() { return mapperLocations; } public void setMapperLocations(String mapperLocations) { this.mapperLocations mapperLocations; }} 2、创建多数据源配置代码如下:java//数据源A//打开Bean注解方法的动态代理功能:该类中带有注解@Bean的方法,都会被动态代理,调用该方法会返回同一个实例;本质上还是@Component@Configuration//这里就是将SqlSessionTemplate与mapper.xml目录进行关联;//其中,sqlSessionTemplateRef配置的就是下面带有@Bean的方法,该方法最终返回了SqlSessionTemplate实例,而且是单例的@MapperScan(basePackages { "com.wj.springboot2demo.domain.searchmanager" }, sqlSessionTemplateRef "searchManagerSqlSessionTemplate")//这个注解的作用是,将application配置文件的属性绑定到了当前类,这里的作用是绑定公共类AbstractDataSourceConfiger中的两个属性。@ConfigurationProperties(prefix "spring.datasource.druid")public class SearchManagerDsConfiger extends AbstractDataSourceConfiger { //当前方法返回的是一个DataSource实例,而且是Spring动态代理的,其他地方通过注解 @Qualifier注入 @Bean(name "searchManagerDataSource") @Primary // 有一个默认的DataSource加此注解,而且只能有一个 // prefix值必须是application.properteis中对应属性的前缀 @ConfigurationProperties(prefix "spring.datasource.druid") public DataSource userDataSource() { DataSource dataSource DruidDataSourceBuilder.create().build(); return dataSource; } @Bean public SqlSessionFactory searchManagerSqlSessionFactory(@Qualifier("searchManagerDataSource") DataSource dataSource) throws Exception { //调用公用方法,根据 DataSource 创建 SqlSessionFactory 对象 return createSessionFactory(dataSource); } //根据 SqlSessionFactory 创建一个 SqlSessionTemplate 对象实例,这里的方法名就是 类注解 @MapperScan 中 sqlSessionTemplateRef 的应用 @Bean public SqlSessionTemplate searchManagerSqlSessionTemplate( @Qualifier("searchManagerSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { SqlSessionTemplate template new SqlSessionTemplate(sqlSessionFactory); // 使用上面配置的Factory return template; }}//数据源B@Configuration@MapperScan(basePackages { "com.wj.springboot2demo.domain.ho" }, sqlSessionTemplateRef "hoSqlSessionTemplate")@ConfigurationProperties(prefix "spring.second-datasource.druid")public class HoDsConfiger extends AbstractDataSourceConfiger { @Bean(name "hoDataSource") @ConfigurationProperties(prefix "spring.second-datasource.druid") public DataSource hoDataSource() { DataSource dataSource DruidDataSourceBuilder.create().build(); return dataSource; } @Bean public SqlSessionFactory hoSqlSessionFactory(@Qualifier("hoDataSource") DataSource dataSource) throws Exception { return createSessionFactory(dataSource); } @Bean public SqlSessionTemplate hoSqlSessionTemplate( @Qualifier("hoSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { SqlSessionTemplate template new SqlSessionTemplate(sqlSessionFactory); // 使用上面配置的Factory return template; }}最终的application.yml配置文件如下:bashserver : port : 8081 spring : datasource : druid : filters : stat driver-class-name: com.mysql.jdbc.Driver 基本属性 url: jdbc:mysql://192.168.50.42:3306/searchmanager?useUnicodetrue&characterEncodingUTF-8&allowMultiQueriestrue username: test password: test 配置初始化大小/最小/最大 initial-size: 1 min-idle: 1 max-active: 20 获取连接等待超时时间 max-wait: 60000 间隔多久进行一次检测,检测需要关闭的空闲连接 time-between-eviction-runs-millis: 60000 一个连接在池中最小生存的时间 min-evictable-idle-time-millis: 300000 validation-query: SELECT 'x' test-while-idle: true test-on-borrow: false test-on-return: false 打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false pool-prepared-statements: false max-pool-prepared-statement-per-connection-size: 20 type-aliases-package : com.wj.springboot2demo.domain.model mapper-locations : classpath:mapper/searchmanager/.xml second-datasource : druid : filters : stat driver-class-name: com.mysql.jdbc.Driver 基本属性 url: jdbc:mysql://192.168.50.42:3306/ho?useUnicodetrue&characterEncodingUTF-8&allowMultiQueriestrue username: test password: test 配置初始化大小/最小/最大 initial-size: 1 min-idle: 1 max-active: 20 获取连接等待超时时间 max-wait: 60000 间隔多久进行一次检测,检测需要关闭的空闲连接 time-between-eviction-runs-millis: 60000 一个连接在池中最小生存的时间 min-evictable-idle-time-millis: 300000 validation-query: SELECT 'x' test-while-idle: true test-on-borrow: false test-on-return: false 打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false pool-prepared-statements: false max-pool-prepared-statement-per-connection-size: 20 mapper-locations : classpath:mapper/ho/.xml 至此,SpringBoot2 + MyBatis 多数据源配置就完成了。[lastSpringBoot]: https://www.oomabc.com/articledetail?atclidabaf81a2f5c246be8e643c45d7867888![图片](https://oomabc.com/staticsrc/img/201809/17/153717666284922e8826e7ddf454e818b96d534bdf7b8.jpg)

    SpringBoot   多数据源   MyBatis   2019-07-26 浏览(3725) 有用(0) 阅读原文>> [原创]
  • SpringBoot2从零开始(三)—— rabbit MQ   

    RabbitMQ是流行的开源消息队列系统,用erlang语言开发。RabbitMQ是AMQP(高级消息队列协议)的标准实现。如果不熟悉AMQP,直接看RabbitMQ的文档会比较困难。不过它也只有几个关键概念,这里简单介绍。几个概念说明: + Broker:简单来说就是消息队列服务器实体。 + Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。 + Queue:消息队列载体,每个消息都会被投入到一个或多个队列。 + Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。 + Routing Key:路由关键字,exchange根据这个关键字进行消息投递。 + vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。 + producer:消息生产者,就是投递消息的程序。 + consumer:消息消费者,就是接受消息的程序。 + channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。消息队列的使用过程大概如下: 1. 客户端连接到消息队列服务器,打开一个channel。 2. 客户端声明一个exchange,并设置相关属性。 3. 客户端声明一个queue,并设置相关属性。 4. 客户端使用routing key,在exchange和queue之间建立好绑定关系。 5. 客户端投递消息到exchange。 exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。 exchange也有几个类型,完全根据key进行投递的叫做Direct交换机,例如,绑定时设置了routing key为abc,那么客户端提交的消息,只有设置了key为abc的才会投递到队列。对key进行模式匹配后进行投递的叫做Topic交换机,符号匹配一个或多个词,符号匹配正好一个词。例如abc.匹配abc.def.ghi,abc.只匹配abc.def。还有一种不需要key的,叫做Fanout交换机,它采取广播模式,一个消息进来时,投递到与该交换机绑定的所有队列。RabbitMQ支持消息的持久化,也就是数据写在磁盘上,为了数据安全考虑,我想大多数用户都会选择持久化。消息队列持久化包括3个部分: 1. exchange持久化,在声明时指定durable 1 2. queue持久化,在声明时指定durable 1 3. 消息持久化,在投递时指定deliverymode 2(1是非持久化) 如果exchange和queue都是持久化的,那么它们之间的binding也是持久化的。如果exchange和queue两者之间有一个持久化,一个非持久化,就不允许建立绑定。![图片](https://oomabc.com/staticsrc/img/201810/21/15401056888798d81059d5f824a10aa4ba2d036433f85.jpg)--- pom文件增加依赖xml org.springframework.boot spring-boot-starter-amqp 修改application.yml文件ymlserver : port : 8081 jetty端口spring配置spring : 其它配置 rabbitmq: addresses: 192.168.50.32:5672 username: admin password: admin virtual-host: test connection-timeout: 15000 publisher-confirms : true confirm模式 publisher-returns : true return机制 template.mandatory : true 与return机制结合配置次属性 消费者配置 listener.simple: acknowledge-model : manual concurrency : 5 max-concurrency : 10 -------- 消息生产者javaimport java.util.Map;import org.springframework.amqp.rabbit.core.RabbitTemplate;import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback;import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;import org.springframework.amqp.rabbit.support.CorrelationData;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.messaging.Message;import org.springframework.messaging.MessageHeaders;import org.springframework.messaging.support.MessageBuilder;import org.springframework.stereotype.Component;/ 生产者 /@Componentpublic class RabbitMqProducer extends BabbitMqTest{ @Autowired private RabbitTemplate rabbitTemplate; final ReturnCallback returnCallback new ReturnCallback() { @Override public void returnedMessage(org.springframework.amqp.core.Message message, int replyCode, String replyText, String exchange, String routingKey) { log("producer ReturnCallback : returnedMessage"); } }; final ConfirmCallback confirmCallback new ConfirmCallback() { //手动确认消息 @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { log("producer ConfirmCallback : " + correlationData + ", " + ack + ", " + cause); } }; / @param messageObject payload @param properties 消息map @see [相关类/方法](可选) @since [产品/模块版本](可选) / public void send(Object messageObject, Map properties) { System.out.println(""); MessageHeaders messageHeaders new MessageHeaders(properties); //消费者接收的内容就是这个 //这里只是按标准定义了一个消息内容,其实这里只要是序列化的对象,都可以 Message message MessageBuilder.createMessage(messageObject, messageHeaders); //消费者ack之后执行 rabbitTemplate.setConfirmCallback(confirmCallback); rabbitTemplate.setReturnCallback(returnCallback); CorrelationData data new CorrelationData("wjyuian123456"); log("producer send message"); //往交换器发消息 rabbitTemplate.convertAndSend("rabbitmq-1", "springboot2.queue", message, data); }}--- 消费者javapackage com.wj.springboot2demo.mq;import java.io.IOException;import org.springframework.amqp.rabbit.annotation.Exchange;import org.springframework.amqp.rabbit.annotation.Queue;import org.springframework.amqp.rabbit.annotation.QueueBinding;import org.springframework.amqp.rabbit.annotation.RabbitHandler;import org.springframework.amqp.rabbit.annotation.RabbitListener;import org.springframework.amqp.support.AmqpHeaders;import org.springframework.messaging.Message;import org.springframework.messaging.MessageHeaders;import org.springframework.stereotype.Component;import com.rabbitmq.client.Channel;/ 消费者 /@Componentpublic class RabbitMqConsumer extends BabbitMqTest { @RabbitListener( //队列绑定信息 bindings @QueueBinding( //队列信息:名称,通知方式 value @Queue(value "springboot2.queue", durable "true"), //绑定的交换器 exchange @Exchange(value "rabbitmq-1", durable "true", type "topic", ignoreDeclarationExceptions "true"), //routingKey key "springboot2" ) ) @RabbitHandler public void onMessage(Message message, Channel channel) throws IOException { Long deliveryTag 0L; try { log("comsumer get message from producer"); //包含消息内容,即Producer中放入的properties对象 //包含消息内容,即Producer中放入的properties对象 MessageHeaders headers message.getHeaders(); deliveryTag (Long) headers.get(AmqpHeaders.DELIVERYTAG); log("comsumer : deliveryTag " + deliveryTag); // log("comsumer : message.getPayload() " + message.getPayload()); String id (String) headers.get("msgid"); Long millis (Long) headers.get("msgmillis"); } catch (Exception e) { e.printStackTrace(); } finally {// log("comsumer act message"); //手动ack //第二个参数:是否批量ack channel.basicAck(deliveryTag, false); //手动拒绝,传入tagId, //第二个参数:是否重新投递个消费者; //false:则本消息结束;true:该消息会继续被消费者消费// channel.basicReject(deliveryTag, true); } }} -------- 使用我这是在Controller中做的测试:java @RequestMapping("/springboot2/rabbit/p") public String rabbitProducer(@RequestParam(defaultValue "defaultV", value "value") String v ,@RequestParam(defaultValue "defaultV", value "value2") String v2) { Map msg new HashMap(); msg.put("msgid", "1111111"); msg.put("msgmillis", System.currentTimeMillis()); rabbitMqProducer.send(v + "message", msg); return "count : ok"; } @Autowired private RabbitMqProducer rabbitMqProducer; -------- 未完待续1. 生产者中相关队列参数细节2. 消费者中相关配置、使用参数细节3. 其它

    rabbitmq   SpringBoot   2019-04-26 浏览(1364) 有用(0) 阅读原文>> [原创]
  • SpringBoot2(四)Docker+Consul+Cloud+Feign   

    前言距离上一篇SpringBoot2的文章,已经过去很久了,今天终于有时间继续写一篇基于SpringBoot2+SpringCloud+consul+feign的入门文章。 Spring Cloud Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。[SpringCloud](https://springcloud.cc/) Spring Cloud Feign Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。通过Feign,Java项目可以轻松实现跨语言的基于Http的服务接口。[Fegin](https://github.com/OpenFeign/feign) Consul Consul是一个服务发现与配置工具,与Docker容器可以无缝集成,同时它自身也是分布式的、高可用的、可扩展的。+ 服务发现:通过Consul,服务提供方可以轻而易举的将自己的服务进行注册,同时可以通过DNS或者HTTP接口来发现其他服务。SaaS等其他服务同样可以注册上来。+ 健康检查:当Consul集群发生任何异常时,健康检查功能可以迅速将异常信息通知给其它节点。它可以防止请求被路由到非健康节点,同时可以提供服务级别的熔断。+ 基于键值的存储:类似Zookeeper,Consul也可以提供键值存储。基于此功能,我们通过简单的HTTP接口,就可以实现分布式配置中心、分布式任务协调、leader选举等诸多分布式能力。+ 多数据中心:进过简单的配置,Consul就可以实现异地多数据中心的能力。+ 服务分区:通过TLS(传输层安全协议)的加密以及基于令牌的权限验证,Consul提供了可靠的端(服务)到端通信。------ Consul安装(单机版)这里的例子中是基于Docker的consul的安装,所以需要先安装Docker环境。参考:[Testing a Consul cluster on a single host](https://hub.docker.com/r/progrium/consul/) 零、首先启动一台Consul实例bash$ docker run -d --name node1 -h node1 progrium/consul -server -bootstrap-expect 3这里通过参数-bootstrap-expect 3指定了本集群有三台Server实例。所以直到这个集群中有另外两台Server角色加入的时候,它才会开始初始化,并且整个集群才是可用状态。 一、准备启动另外两台Server在启动另外两台Server之前,我们需要先获得第一台Server对应的容器IP,并将它设置到docker环境变量中。bash$ JOINIP"$(docker inspect -f '{{.NetworkSettings.IPAddress}}' node1)"接下来通过docker来启动另外两台Server即可:bash$ docker run -d --name node2 -h node2 progrium/consul -server -join $JOINIPbash$ docker run -d --name node3 -h node3 progrium/consul -server -join $JOINIP注:通过docker inspect -f '{{.NetworkSettings.IPAddress}}' 容器name或者ID命令,就可以看到对应容器的IP地址。 二、Client暴露服务至此,我们已经安装好有三个Server节点的Consul集群了。不过此时,我们并没有为这个集群暴露可访问端口。所以我们需要通过第四个节点以Client(去掉-server参数)身份加入到集群中。Client节点不会参与集群管理,只会参与通信,同时也不会将数据持久化。bash$ docker run -d -p 8400:8400 -p 8500:8500 -p 8600:53/udp --name node4 -h node4 progrium/consul -join $JOINIP此时我们暴露了几个端(8400 (RPC)、8500 (HTTP)、8600 (DNS) )口以实现不同协议的通信。 三、多服务集群参考文档 [Running a real Consul cluster in a production environment](https://hub.docker.com/r/progrium/consul)可以通过以下命令生成启动命令:bashdocker run --rm progrium/consul cmd:run 10.0.1.1 -ddocker run --rm progrium/consul cmd:run 10.0.1.1::10.0.1.2 -d -v /mnt:/data在Consul集群安装成功之后,我们可以访问web页面查看集群状态。我这里是将集群安装在192.168.100.185单机上,访问地址http://192.168.100.185:8500/ui//dc1/nodes/node4?condensedfalse。出现如下页面,表示安装成功。![blockimg](https://oomabc.com/staticsrc/img/201904/30/15565605850603471e01059d04eafb4dc44e484a5c551.jpg)接下来在SpringBoot项目中增加Consul配置。------ 添加SpringCloud、Consul配置 零、在pom.xml文件增加如下配置xml org.springframework.cloud spring-cloud-consul-dependencies 2.0.0.M7 pom import org.springframework.cloud spring-cloud-openfeign 2.0.0.RC1 pom import org.springframework.cloud spring-cloud-starter-consul-discovery org.springframework.boot spring-boot-starter-actuator compile org.springframework.cloud spring-cloud-starter-openfeign 一、配置SpringCloud、Consul在application.yml配置文件中增加cosul配置bashspring : application : name : test-springcloud 注册springcloud时候的服务名称,在下面serviceName使用 cloud: consul: consul集群相关配置 host: 192.168.100.185 port: 8500 discovery: 服务发现配置 register: true 注册服务打开 serviceName: ${spring.application.name} 服务名称 healthCheckPath: /actuator/health 开启服务健康检查 healthCheckInterval: 15s 检查间隔 tags: urlprefix-/${spring.application.name} instanceId: ${spring.application.name}:aa356dsfe21312fafd00asdf 服务唯一标识在启动类上增加注解:java//服务注册、服务发现@EnableDiscoveryClient@EnableFeignClients@SpringBootApplication//myBatis的mapper扫描路径public class Springboot2demoApplication { public static void main(String[] args) { SpringApplication.run(Springboot2demoApplication.class, args); }}启动项目,我们可以在之前的consul界面看到服务注册成功了。图片如下:![blockimg](https://oomabc.com/staticsrc/img/201904/30/1556560619868f8d96113bd3d4e6a8390cc2072c3bd3b.jpg) 二、增加服务发现测试代码我们写一个Controller,来测试服务发现功能:java@RestControllerpublic class CloudConsulController { @Autowired private DiscoveryClient discoveryClient; / 获取所有服务 / @RequestMapping("/services") public Object services() { return discoveryClient.getServices(); } @RequestMapping("/services/list") public Object servicesList() { return discoveryClient.getInstances("test-springcloud"); }}重新启动项目之后,通过链接http://localhost:8081/services/list可以看到发现的服务:js// 20190426111730// http://localhost:8081/services/list[ { "serviceId": "test-springcloud", "host": "10.10.1.102", "port": 8081, "secure": false, "metadata": { "urlprefix-/test-springcloud": "urlprefix-/test-springcloud" }, "uri": "http://10.10.1.102:8081", "scheme": null }]------ 消费者feign对接服务 Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。所以我们新建一个接口,然后自定义接口以及相关注解,即可对接已提供的服务。java//cloud注册到consul的application-name@FeignClient("test-springcloud")public interface FeignService { //服务对应的url @RequestMapping(value "/springboot2/mysql") String hello(@RequestParam(name "value") String v); //这里对接了一个参数名为value的参数}这里配置的RequestMapping的value值,就是我们再controller中提供的mvc接口。java@RestControllerpublic class FirstController { //........... @RequestMapping("/springboot2/mysql") public String mysql(@RequestParam(defaultValue "defaultV", value "value") String v ,@RequestParam(defaultValue "defaultV", value "value2") String v2) { TbSearchManagerUserExample example new TbSearchManagerUserExample(); int count tbSearchManagerUserMapper.countByExample(example); System.out.println(count + " " + v); example.createCriteria().andUserIdEqualTo(2L); TbSearchManagerUser record new TbSearchManagerUser(); record.setNickName("SpringBoot"); int rs tbSearchManagerUserMapper.updateByExampleSelective(record, example); System.out.println("searchManagerUser count : " + rs); return "count : " + count + " " + v; } //...........}然后我们在测试Controller里面对这个Feign客户端进行测试:java@RestControllerpublic class CloudConsulController { //......... @Autowired private FeignService feignService; @RequestMapping(value "/feign/mysql", method RequestMethod.GET) public Object consumerCloud() { return feignService.hello("testcloud"); }}这个项目把服务提供者和消费者整合在了一起,重启启动项目。然后访问./feign/mysql对应的链接http://localhost:8081/feign/mysql,返回结果count : 45 testcloud。这个结果与我们直接访问服务端mvc接口http://localhost:8081/springboot2/mysql?valuetestcloud的结果,是一致的。至此,SpringBoot2+SpringCloud+Consul+Feign的整合,已经初步完成了。

    SpringCloud   Consul   Feign   Docker   SpringBoot   2019-09-22 浏览(917) 有用(0) 阅读原文>> [原创]
  • blogTest
    分享文章
     
    使用APP的"扫一扫"功能,扫描左边的二维码,即可将网页分享给别人。
    你也可以扫描右边本博客的小程序二维码,实时关注最新文章。