Spring加Mybatis实现MySQL数据库主从读写分离

上周在一个同事的指点下,实现了Spring加Mybatis实现了MySQL的主从读写分离,今天记一下笔记,以供自己今后参考,下面是配置文件的写法。 数据源也就是jdbc.properties,因为是主从读写分离,那么肯定有两个数据源了 jdbc.driver=org.mariadb.jdbc.Driver \# 从库,只读 slave.jdbc.url=jdbc:mariadb://xxx.xxx.xxx.xxx:3306/xxx?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&noAccessToProcedureBodies=true&autoReconnect=true slave.jdbc.username=xxx slave.jdbc.password=xxx \# 主库,需要写 master.jdbc.url=jdbc:mariadb://xxx.xxx.xxx.xxx:3306/xxx?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&noAccessToProcedureBodies=true&autoReconnect=true master.jdbc.username=xxx master.jdbc.password=xxx 这个非常简单和普通的区别不是很大,另外数据库的驱动是:mariadb,动下面就是第二个配置文件spring.xml spring.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd "> <!&#8211; Import properties file &#8211;> <context:property-placeholder location="classpath:jdbc.properties" /> <!&#8211; Auto Scan &#8211;> <context:component-scan base-package="cn.bridgeli.demo" /> </beans> 这个文件很简单,有两个作用,①. 引入数据源配置文件;②. spring扫描的文件的路径 spring-mybatis.xml配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd "> <bean id="slaveDataSourceImpl" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${slave.jdbc.url}" /> <property name="username" value="${slave.jdbc.username}" /> <property name="password" value="${slave.jdbc.password}" /> <!&#8211; 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 &#8211;> <property name="idleConnectionTestPeriodInMinutes" value="10" /> <!&#8211; 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 &#8211;> <property name="idleMaxAgeInMinutes" value="10" /> <!&#8211; 每个分区最大的连接数 &#8211;> <property name="maxConnectionsPerPartition" value="20" /> <!&#8211; 每个分区最小的连接数 &#8211;> <property name="minConnectionsPerPartition" value="10" /> <!&#8211; 分区数 ,默认值2,最小1,推荐3-4,视应用而定 &#8211;> <property name="partitionCount" value="3" /> <!&#8211; 每次去拿数据库连接的时候一次性要拿几个,默认值:2 &#8211;> <property name="acquireIncrement" value="3" /> <!&#8211; 缓存prepared statements的大小,默认值:0 &#8211;> <property name="statementsCacheSize" value="50" /> <!&#8211; 在做keep-alive的时候的SQL语句 &#8211;> <property name="connectionTestStatement" value="select 1 from dual" /> <!&#8211; 在每次到数据库取连接的时候执行的SQL语句,只执行一次 &#8211;> <property name="initSQL" value="select 1 from dual" /> <property name="closeConnectionWatch" value="false" /> <property name="logStatementsEnabled" value="true" /> <property name="transactionRecoveryEnabled" value="true" /> </bean> <bean id="masterDataSourceImpl" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${master.jdbc.url}" /> <property name="username" value="${master.jdbc.username}" /> <property name="password" value="${master.jdbc.password}" /> <!&#8211; 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 &#8211;> <property name="idleConnectionTestPeriodInMinutes" value="10" /> <!&#8211; 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 &#8211;> <property name="idleMaxAgeInMinutes" value="10" /> <!&#8211; 每个分区最大的连接数 &#8211;> <property name="maxConnectionsPerPartition" value="20" /> <!&#8211; 每个分区最小的连接数 &#8211;> <property name="minConnectionsPerPartition" value="10" /> <!&#8211; 分区数 ,默认值2,最小1,推荐3-4,视应用而定 &#8211;> <property name="partitionCount" value="3" /> <!&#8211; 每次去拿数据库连接的时候一次性要拿几个,默认值:2 &#8211;> <property name="acquireIncrement" value="3" /> <!&#8211; 缓存prepared statements的大小,默认值:0 &#8211;> <property name="statementsCacheSize" value="50" /> <!&#8211; 在做keep-alive的时候的SQL语句 &#8211;> <property name="connectionTestStatement" value="select 1 from dual" /> <!&#8211; 在每次到数据库取连接的时候执行的SQL语句,只执行一次 &#8211;> <property name="initSQL" value="select 1 from dual" /> <property name="closeConnectionWatch" value="false" /> <property name="logStatementsEnabled" value="true" /> <property name="transactionRecoveryEnabled" value="true" /> </bean> <!&#8211; DataSource/Master &#8211;> <bean id="masterDataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"> <property name="targetDataSource" ref="masterDataSourceImpl" /> </bean> <bean id="masterTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="masterDataSource" /> </bean> <bean id="masterTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="masterTransactionManager" /> </bean> <!&#8211; DataSource/Slave &#8211;> <bean id="slaveDataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"> <property name="targetDataSource" ref="slaveDataSourceImpl" /> </bean> <bean id="slaveTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="slaveDataSource" /> </bean> <bean id="slaveTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="slaveTransactionManager" /> </bean> <!&#8211; Mybatis/Master &#8211;> <bean id="masterSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="masterDataSource"></property> <property name="configLocation" value="classpath:mybatis.xml" /> <property name="typeAliasesPackage" value="cn.bridgeli.demo.entity" /> <property name="mapperLocations"> <list> <value>classpath:cn/bridgeli/demo/mapper/master/*.xml</value> </list> </property> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.bridgeli.demo.mapper.master" /> <property name="sqlSessionFactoryBeanName" value="masterSqlSessionFactory" /> </bean> <!&#8211; Mybatis/Slave &#8211;> <bean id="slaveSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="slaveDataSource"></property> <property name="configLocation" value="classpath:mybatis.xml" /> <property name="typeAliasesPackage" value="cn.bridgeli.demo.entity" /> <property name="mapperLocations"> <list> <value>classpath:cn/bridgeli/demo/mapper/slave/*.xml</value> </list> </property> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.bridgeli.demo.mapper.slave" /> <property name="sqlSessionFactoryBeanName" value="slaveSqlSessionFactory" /> </bean> <!&#8211; Configuration transaction advice &#8211;> <tx:advice id="txAdvice" transaction-manager="masterTransactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="get*" read-only="true" propagation="SUPPORTS" /> <tx:method name="list*" read-only="true" propagation="SUPPORTS" /> </tx:attributes> </tx:advice> <!&#8211; Configuration transaction aspect &#8211;> <aop:config> <aop:pointcut id="systemServicePointcut" expression="execution(\* cn.bridgeli.demo.service.\*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="systemServicePointcut" /> </aop:config> </beans> 这个配置文件老夫是完整的copy了下来,看起来也比较易懂,就不做解释了,需要说明的mybatis下那些dao的接口,分别对应cn.bridgeli.demo.mapper.master、cn.bridgeli.demo.mapper.slave,cn.bridgeli.demo.mapper.master下的这些dao接口是要写的,另一个是读的,这些接口对应的配置文件肯定就是他们对应的文件夹下面的xml文件了,在将来的项目中几乎可以照抄 ...

May 3, 2015 · 3 min · 502 words · Bridge Li

MyBatis下最好的分页实现:mybatis-paginator使用入门

前两天写一个项目,发现在MyBatis下一个最好的分页实现类库mybatis-paginator,今天就写一篇其入门教程供大家参考。 先引入maven依赖 <dependency> <groupId>com.github.miemiedev</groupId> <artifactId>mybatis-paginator</artifactId> <version>1.2.15</version> </dependency> 从这个依赖中,我们可以看到 1 他是mybatis的一个插件,2 其开源在GitHub上,感兴趣的可以去GitHub上搜源码看看,既然是mybatis的插件,下一步肯定就是和mybatis的集成了. 和mybatis集成 ①. 新建一个mybatis.xml的配置文件,其内容如下: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="cacheEnabled" value="true" /> <setting name="lazyLoadingEnabled" value="true" /> </settings> <plugins> <plugin interceptor="com.github.miemiedev.mybatis.paginator.OffsetLimitInterceptor"> <property name="dialectClass" value="com.github.miemiedev.mybatis.paginator.dialect.MySQLDialect" /> <property name="asyncTotalCount" value="true" /> </plugin> </plugins> </configuration> ②. 和spring集成,即在spring-mybatis.xml中引入该文件 &#8230;&#8230; <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="configLocation" value="classpath:mybatis.xml" /> <property name="typeAliasesPackage" value="cn.bridgeli.demo.entity" /> <property name="mapperLocations" value="classpath:cn/bridgeli/demo/mapper/*.xml" /> </bean> &#8230;&#8230; 因为这个文件太长,就不贴全文了,仅把位置这一块贴出来,当我们把这些配置工作做好之后,下一步就是如何使用了。 ...

April 26, 2015 · 2 min · 219 words · Bridge Li

正则表达式入门

今天讲讲正则表达式,正则表达式在编程中是非常常用的一项技术,也是非常行之有效的技术,有了他,很多复杂的问题就变得的非常简单了,常见的用途有:字符串匹配(或者叫字符匹配)、字符串查找、字符串替换,典型应用有:用户注册时用户名和密码的验证、检测IP地址是否正确,从网页中揪出链接等等,从常见用途中我们看到,一言以蔽之,正则表达式就是对字符串的处理,所以正则表达式牵涉到的类有三个:java.lang.String、java.util.regex.Pattern、java.util.regex.Matcher,其实正则的用途和功能非常强大,今天老夫就写一些最基本的用法,其实那些高级用法也是从这些基本用法来的,今后看看有没有机会写一下高级用法(主要是老夫现在也不会,,,),下面我们来看看这些最基本的语法: 字符类 [abc] a、b 或 c(简单类) [^abc] 任何字符,除了 a、b 或 c(否定) [a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围) [a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](并集) [a-z&&[def]] d、e 或 f(交集) [a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](减去) [a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](减去) 预定义字符类 . 任何字符(与行结束符可能匹配也可能不匹配) d 数字:[0-9] D 非数字: [^0-9] s 空白字符:[ tnx0Bfr] S 非空白字符:[^s] w 单词字符:[a-zA-Z_0-9] W 非单词字符:[^w] 边界匹配器 ^ 行的开头 $ 行的结尾 b 单词边界 B 非单词边界 A 输入的开头 G 上一个匹配的结尾 Z 输入的结尾,仅用于最后的结束符(如果有的话) z 输入的结尾 Greedy 数量词 X? X,一次或一次也没有 X* X,零次或多次 X+ X,一次或多次 X{n} X,恰好 n 次 X{n,} X,至少 n 次 X{n,m} X,至少 n 次,但是不超过 m 次 反斜线、转义和引用 反斜线字符 (&#8221;) 用于引用转义构造,如上表所定义的,同时还用于引用其他将被解释为非转义构造的字符。因此,表达式 \ 与单个反斜线匹配,而 { 与左括号匹配。 在不表示转义构造的任何字母字符前使用反斜线都是错误的;它们是为将来扩展正则表达式语言保留的。可以在非字母字符前使用反斜线,不管该字符是否非转义构造的一部分。 根据 Java Language Specification 的要求,Java 源代码的字符串中的反斜线被解释为 Unicode 转义或其他字符转义。因此必须在字符串字面值中使用两个反斜线,表示正则表达式受到保护,不被 Java 字节码编译器解释。例如,当解释为正则表达式时,字符串字面值 "b" 与单个退格字符匹配,而 "\b" 与单词边界匹配。字符串字面值 "(hello)" 是非法的,将导致编译时错误;要与字符串 (hello) 匹配,必须使用字符串字面值 "\(hello\)"。 其实正则表达式的用法说说难不难,但说简单一点也不简单,下面是一个例子对以上的这些语法进行测试,当我们想不起来的时候,可以把这些例子拷出来跑一下,看一下效果就知道了 ...

April 6, 2015 · 2 min · 416 words · Bridge Li

JAVA 性能调优

学习Java性能调优之前,我们必须得先了解Java中的内存分配:堆、栈、非堆 为了更好的说明这个问题,我们先看一个程序: package cn.bridgeli.demo; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; public class Test { public void test() { List<Email> emails = new ArrayList<Email>(); Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { Class.forName(""); String url = ""; conn = DriverManager.getConnection(url, "", ""); pstmt = conn.prepareStatement(""); rs = pstmt.executeQuery(); Email email = null; while (rs.next()) { email = new Email(); email.setSubject(""); emails.add(email); } } catch (Exception e) { e.printStackTrace(); } finally { // close conn } } } 在这段代码中,那些哪些数据放在堆上,哪些数据放在栈上,又有哪些数据放在方法去呢?一言以蔽之:在方法中,“=”左边的值,全部放在栈上,占4个字节,而“=”右边的肯定就是放在堆上了,所以该段代码中像:emails、conn的变量都是放在栈上的,而我们 new 出来的全部放在了堆上,而方法区是没有放东西的,既然new 出来的Email是放在堆上的,那么Email中的这些变量又是放在哪呢? ...

March 29, 2015 · 2 min · 320 words · Bridge Li

SpringMVC中Interceptor和自定义filter的典型应用

今天写写老夫最擅长的Java web,在Java web中Interceptor和filter应用十分广泛,今天就写一个在我们的项目中的一个最基本的应用,过滤或者拦截未登录用户访问某些资源。 SpringMVC中Interceptor SpringMVC 中的Interceptor 拦截器是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理。比如通过它来进行权限验证,或者是来判断用户是否登陆等等。今天就写一个Interceptor在开发中的典型应用:某一系统某些方法肯定是需要用户登陆才能访问的,而另外一些肯定不需要用户登陆就能访问(这样的例子很多,老夫就不举例说明了),那么我们怎么做,才能做到呢?这个时候Interceptor就派上用场了,下面是一个小例子,供参考: spring-servlet.xml核心代码如下: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"> <mvc:interceptors> <bean id="permissionInterceptor" class="cn.bridgeli.demo.interceptor.PermissionInterceptor"></bean> </mvc:interceptors> &#8230;&#8230; </beans> 对应的Interceptor的实现: package cn.bridgeli.demo.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndViewDefiningException; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.util.UrlPathHelper; import cn.bridgeli.demo.entity.User; public class PermissionInterceptor extends HandlerInterceptorAdapter { private UrlPathHelper urlPathHelper = new UrlPathHelper(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { User user = (User) request.getSession().getAttribute("USER"); String url = urlPathHelper.getLookupPathForRequest(request); int flag = url.indexOf("/admin/"); if (user == null && flag != -1) { ModelAndView mav = new ModelAndView("error/permissionerror"); mav.addObject("ERRORMSG", "对不起,您没有登录,无法使用该功能!"); throw new ModelAndViewDefiningException(mav); } return true; } } 关于InterceptorAdapter的更多用法,大家可以参考http://haohaoxuexi.iteye.com/blog/1750680,老夫以为这篇文章说的相对比较详细易懂,除此之外,我们还可以通过自定义filter来实现; ...

March 8, 2015 · 2 min · 220 words · Bridge Li

软件属性小结

一. 功能属性 Use Story和Use Case 功能的三个要素 ①. 参与者 用户、角色、用户和角色的关系 ②. 流程 整体流程、页面操作流程 ③. 表单(UI) 正确性(精确性) 二. 决定与架构 权衡软件质量属性 架构元素 ①. 架构(决定)过程 ②. 架构(决定)产物 架构(架构元素集合) 三. 质量属性(非功能性属性) 开发期质量属性 ①. 易理解性和可读性 所有烦人工作成果(如需求文档、设计文档、code等)易读、易理解,可以提高团队开发性率,降低维护成本 考虑的因素:拒绝啰里啰嗦、复杂问题简单化、拒绝学术化、严格遵守codestyle(一旦定下来,团队所有成员必须无条件遵守!!!) ②. 可扩展性 可扩展性是软件适应变化的能力,在软件开发过程中变化时司空见惯的,如需求、设计、算法的改进,程序的变化的等等。 考虑的因素:增量开发、小型大型软件、是否有下一个版本 ③. 可重用性 重复利用软件的中某一个组件(如文档模板、架构框架、代码)的能力 考虑的因素:架构(框架)重用、模块重用、重用与分层 ④. 可测试性 软件测试的难易程度,软件的可测试性是指软件发生故障并隔离、定位其故障的能力特性,以及在一定的时间和成本的前提下,进行测试设计、测试执行的能力,例如Controller层不要和request、response等耦合、表单等要有Id(为了自动化测试)等 ⑤. 可维护性 可维护性是指理解、改正、改动、改进软件的难易程度,影响可维护性的因素有:可理解性、可测试性、可扩展性 改正性维护:软件在使用中发现了隐藏的错误后,为了诊断和改正这个隐藏错误而修改软件的活动 适应性维护:为了适应变化了的环境而修改软件的活动,如:数据库、操作系统、服务器网络带宽等 完善性维护:为了扩充或完善原有软件功能或性能而改动软件的活动 预防性维护:为了提高软件的可维护性和可靠性,为未来的进一步改进打下基础而修改的活动 ⑥. 可移植性 是指软件不经修改或稍加修改就可以运行于不同软硬件环境(CPU、OS)的能力,主要是代码的可移植性 考虑的因素:硬件之间、数据库之间 ⑦. 兼容性 不同软件或新老版本之间交换信息的能力 考虑的因素:版本之间的兼容、软件之间的兼容,例如:老的客户端能否调用新的服务器版本的API,office和wps兼容性问题(这个存在强势问题,例如微软比较强大、用户比较多,我们就可以不兼容你们金山) 运行期质量属性 ①. 性能 性能通常是指软件的“时间-空间”效率,而不是软件的运行速度。人们总希望软件的运行速度高些,并且占用资源少些,一言以蔽之:既要马儿跑得快,又要马儿吃得少,即性价比最高。 性能优化的关键是:找出限制性能的瓶颈,原则是:管理好自己,控制好别人,那么自己是谁?别人有是谁呢?自己一般是进程(拥有)、内存(分配给自己的)、程序;别人一般是:数据库、文件、其他系统、网络。 控制别人,对外资源(数据库连接、文件流、socket等)一定要关闭,而且在finally里面关闭,当然一些连接可以使用池的概念。 管理好自己,数据库调优、JVM调优、堆栈内存大小的设置等这是一个很大的概念,希望将来能有一篇专门的文章来写这个,其实关于这个网上的资料也挺多的,大家可以自己搜一些来自学一下 ②. 安全性 这里的安全性是指信息安全,英文原文是指:security而不是safety,安全性是指防止系统被非法入侵的能力,即属于技术问题又属于安全问题。那么什么样的系统是安全的呢?一般的,如果黑客为非法入侵花费的代价(时间、费用、风险等等)高于得到的好处,那么这样的系统可以认为是安全的 考虑的因素:同源策略、SQL输入、跨站脚本攻击、跨站请求伪造、加密解密技术、API安全性,这些每一个都是一门学问,大家可以自己都一些资料,自己去学习一下 ③. 易用性 用户(是指最终用户)使用软件的容易程度,最终用户并不关心软件是怎么实现的,他们只关心UI、操作的方便性、流程的简易程度 ④. 可用性 这个比较难描述,只要出现问题就是不可用的,例如:银行系统,A用户向B用户转账,A的钱扣了,B却没收到等等 ⑤. 可伸缩性 代表一种弹性,在系统扩展成长的过程中,软件能够保证旺盛的生命力,通过很少的改动甚至只是硬件设备的添置,就能实现整个系统处理能力的线性增长,实现高吞吐量和低延迟的高性能。 考虑的因素:数据库是否支持集群、web服务器的集群(session本地问题、文件本地化问题) ⑥. 互操作性 一般是指对外提供API的调用难易,例如一些开放平台 ⑦. 可靠性 在给定的条件下,在给定的时间内,系统不发生故障的概率,可靠性问题一出现一般是很难发现的,他出现无规律,时隐时现 ⑧. 健壮性 是指在异常情况下,软件能够正常工作的能力 异常:与预期产生不同结果都叫异常,预期:和用户的预期、QA的预期、需求文档的预期(主要是这个,即运行结果和需求文档描述不一致) 例如:用户注册输入一个已存在的用户名,怎么做?(应该提示用户,不能抱一个505错误)、数据库宕机、网络慢等情形下该怎么做? 容错能力:异常发生后,软件能否正常运行,例如:数据库主从备份、多服务器集群,用户输入错误的数据,能提示用户 恢复能力:例如数据备份,当一个数据库宕机之后,另一个备份数据库能自己立马启动,接下宕机服务器的任务继续工作

December 28, 2014 · 1 min · 91 words · Bridge Li

数据加密算法之MD5和SHA1

这个星期记录一下数据加密算法,记得刚开始学编程的时候就有一个疑问:我们的密码就这么放在数据库里面,多不安全啊,数据库管理员不是拿着数据想干嘛就干嘛吗?但是由于认知有限,一直没有解决这个问题,直到去年实习时,当时的项目经理Zack说,用户密码不能明文存放到数据,必须经过MD5加密,终于解决了这个问题。因为MD5的不可逆性,所以就算知道MD5码,只要你不是一些弱密码,一般情况下发生泄密的可能性是非常非常小的,几乎可以认为是绝对安全的,但MD5实现的实现却很简单,今天就记录一下实习时用到的一个MD5加密算法的一个实现: package cn.bridgeli.demo; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class Encryption { public static String MD5(String input) { StringBuffer hexString = null; try { // 获得MD5摘要算法的 MessageDigest 对象 MessageDigest mdInst = MessageDigest.getInstance("MD5"); // 使用指定的字节更新摘要 mdInst.update(input.getBytes()); // 获得密文 byte[] md = mdInst.digest(); // 把密文转换成十六进制的字符串形式 hexString = new StringBuffer(); // 字节数组转换为 十六进制 数 for (int i = 0; i < md.length; i++) { String shaHex = Integer.toHexString(md[i] & 0xFF); if (shaHex.length() < 2) { hexString.append(0); } hexString.append(shaHex); } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return hexString.toString(); } public static String SHA1(String decript) { StringBuffer hexString = null; try { MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1"); digest.update(decript.getBytes()); byte messageDigest[] = digest.digest(); // Create Hex String hexString = new StringBuffer(); // 字节数组转换为 十六进制 数 for (int i = 0; i < messageDigest.length; i++) { String shaHex = Integer.toHexString(messageDigest[i] & 0xFF); if (shaHex.length() < 2) { hexString.append(0); } hexString.append(shaHex); } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return hexString.toString(); } } 这里面还有一个SHA1机密算法的实现,主要是在自学微信开发时,微信在接入验证的数据经过字典排序后SHA1加密,所以就顺便记录了一下,算是两个比较常用的加密算法吧,供大家参考。 ...

December 21, 2014 · 1 min · 159 words · Bridge Li

全文检索工具-Lucene(solr)入门

最近闲着没事在写微信公众号,其中一个是聊天机器人,和网上的众多机器人原理一样,但是功能没那么强大(主要是只是库不够强大),但是怎么解决“如何根据用户的问题从回答库中找出最匹配的答案呢?”,大家最先想到的也许是数据库的 LIKE 就好了嘛,但是 LIKE 存在如下问题: 在问答库非常庞大的时候,LIKE 的效率会非常非常的慢; LIKE只适用于关键字匹配,并不适合自然语言匹配。举个例子:用户的问题“河南的省会是哪个城市?”,而数据库的的记录是“河南的省会是哪”,虽然无论是从字面上还是意义上都一样,都 LIKE 却无能为力; LIKE 无法计算相似度。也就是说 LIKE 返回多条记录时,无法确定那个是最佳答案所以此时全文检索引擎的优越性就体现出来了。 全文检索引擎的原理:扫描知识库的每一条记录并分词建立索引,索引记录了词在每一条记录中出现的位置和次数,当收到用户的问题时,也进行分词,然后从索引中找出包含这些词的所有记录,再分别计算相似度,然后可以找出相似度最高的一条记录返回给用户,下面老夫给出一个自己用Lucene(solr)写的例子,这个例子经验证是可以直接跑起来的,至于其众多API,大家可以自己去查官网文档,其实很容易理解。 import java.io.File; import java.io.IOException; import java.util.List; import javax.annotation.PostConstruct; import javax.annotation.Resource; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.IntField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.wltea.analyzer.lucene.IKAnalyzer; import cn.bridgeli.livingsmallhelper.entity.Knowledge; import cn.bridgeli.livingsmallhelper.mapper.KnowledgeMapper; import cn.bridgeli.livingsmallhelper.service.ChatService; public class SolrTest { private static final Logger LOG = LoggerFactory.getLogger(DataInit.class); private KnowledgeMapper knowledgeMapper; @Resource private ChatService chatService; public void createIndex() { List<Knowledge> knowledges = knowledgeMapper.query(); Directory directory = null; IndexWriter indexWriter = null; File indexFile = new File(chatService.getIndexDir()); if (!indexFile.exists()) { try { directory = FSDirectory.open(indexFile); IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_46, new IKAnalyzer(true)); indexWriter = new IndexWriter(directory, indexWriterConfig); for (Knowledge k : knowledges) { Document document = new Document(); document.add(new TextField("question", k.getQuestion(), Field.Store.YES)); document.add(new IntField("id", k.getId(), Field.Store.YES)); document.add(new TextField("answer", k.getAnswer() == null ? "" : k.getAnswer(), Field.Store.YES)); document.add(new IntField("category", k.getCategory(), Field.Store.YES)); indexWriter.addDocument(document); } indexWriter.commit(); } catch (IOException e) { LOG.error("IOException", e); } finally { try { if (null != indexWriter) { indexWriter.close(); } if (null != directory) { directory.close(); } } catch (IOException e) { LOG.error("IOException", e); } } } } @SuppressWarnings("deprecation") private Knowledge searchIndex(String content) { Knowledge knowledge = null; Directory directory = null; IndexReader reader = null; try { directory = FSDirectory.open(new File(getIndexDir())); reader = IndexReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); //这个question 就是以问题为索引去查找,和CreateIndex()中相对应 QueryParser queryParser = new QueryParser(Version.LUCENE_46, "question", new IKAnalyzer(true)); Query query = queryParser.parse(QueryParser.escape(content)); //这个1 的含义就是找出最相似度最高的一条 TopDocs topDocs = searcher.search(query, 1); if (topDocs.totalHits > 0) { knowledge = new Knowledge(); ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { Document doc = searcher.doc(scoreDoc.doc); knowledge.setId(doc.getField("id").numericValue().intValue()); knowledge.setQuestion(doc.get("questory")); knowledge.setAnswer(doc.get("answer")); knowledge.setCategory(doc.getField("category").numericValue().intValue()); } } } catch (IOException e) { LOG.error("IOException", e); } catch (ParseException e) { LOG.error("ParseException", e); } finally { try { reader.close(); directory.close(); } catch (IOException e) { LOG.error("IOException", e); } } return knowledge; } @Override public String getIndexDir() { String classpath = SolrTest.class.getResource("/").getPath(); classpath = classpath.replaceAll("%20", " "); LOG.warn("==================" + classpath); return classpath + "index/"; } @Test public void testSolr() throws IOException, ParseException { SolrTest solrTest = new SolrTest(); File indexDir = new File(getIndexDir()); if (!indexDir.exists()) { solrTest.createIndex(indexDir); } solrTest.searchIndex("你好啊"); } } 因为我是用maven写的,所以对应的pom文件,如下: ...

November 2, 2014 · 2 min · 370 words · Bridge Li

Spring mvc中的forward和redirect以及参数传递

forward和redirect 大家都知道servlet在处理完业务逻辑返回时有两种方法forward和redirect,他们的差异相信不用我再多做解释(如果不知道的请自行谷歌,哪怕是百度也可以),而Spring mvc是对servlet的一种封装,那spring mvc默认采用的是哪一种呢?我们是否可以自己选择采用哪一种方式返回呢?还有我之前在用spring mvc 都是返回到某一个view,它是否可以访问另一个controller呢?针对第一个问题,我们可以看一下spring mvc 的配置文件便知分晓: <property name="viewResolvers"> <list> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".jsp"/> </bean> </list> </property> 因为WEB-INF是一个受保护的目录,客户端是访问不到的,只能通过服务器端访问,根据forward和redirect的区别,我们很容易看到是forward的方式返回的,针对第二个和第三个问题,其实也很简单,我们只需要让该controller返回String即可,然后在方法的最后 return “redirect:/game”;或者return “forward:/game”;即 redirect或者forward + “:” + “/” + controller的路径或者view的名字即可。 参数传递 因为公司的项目是用Spring mvc开发的,发现参数传递除了通过model,还可以通过 RedirectAttributes,据说参数的传递和跳转的URL后面带的值会有一定的关系,这个我没具体测试,感兴趣的可以自己测测。 总结:这篇文章是近期学到的知识点,以前感觉自己会用Spring mvc了,今天才发现还有好多Spring mvc的特性不知道,值得自己去探索啊!

October 24, 2014 · 1 min · 40 words · Bridge Li

Java中的split() replace() replaceFirst() replaceAll()四个函数分析

前几天在公司分割一个很简单字符串,结果却怎么测都不对,最后查了一下资料,终于发现了端倪: split(regex); replace(target, replacement); replace(oldChar, newChar); replaceFirst(regex, replacement); replaceAll(regex, replacement) 仔细看一下,你会发现split()、replaceFirst()、replaceAll()的参数都是Regular Expression,也就是正则表达式,只有replace()的参数是字符或者字符串,由于这些参数类型的差异,很有将得不到预期的结果,下面是一些测试代码的例子,大家可以自己测一下 package cn.bridgeli.stringtest; import org.junit.Test; public class StringTest { @Test public void testSplit1() { String str = "111|222|333|444"; String[] result = str.split("|"); for (String string : result) { System.out.println(string); } // String str = "111|222|333|444"; // String[] result = str.split("\|"); // for (String string : result) { // System.out.println(string); // } } @Test public void testSplit2() { String str = "111,222,333,444"; String[] result = str.split("\d"); for (String string : result) { System.out.println(string); } // String str = "111\222\333\444"; // String[] result = str.split("\\"); // for (String string : result) { // System.out.println(string); // } } @Test public void testSplit3() { String str = "111222333444"; String[] result = str.split("\"); for (String string : result) { System.out.println(string); } // String str = "111\222\333\444"; // String[] result = str.split("\\"); // for (String string : result) { // System.out.println(string); // } } @Test public void testReplaceAll() { String str = "111,222,333,444"; String result = str.replaceAll(",", "$"); System.out.println(result); // String str = "111,222,333,444"; // String result = str.replaceAll(",", "\$"); // System.out.println(result); } }

October 20, 2014 · 1 min · 179 words · Bridge Li