关于 tomcat 排查错误的一个小小感悟

前几天响应公司的要求,系统日志接入公司的 ELK,按照中间件的同学要求之后,果然不出意外的遇到了问题,项目跑不起来了,控制台 catalina.out 打印日志如下: Aug 16, 2018 10:02:21 AM org.apache.coyote.AbstractProtocol start INFO: Starting ProtocolHandler ["http-apr-8144"] Aug 16, 2018 10:02:21 AM org.apache.coyote.AbstractProtocol start INFO: Starting ProtocolHandler ["ajp-apr-58144"] Aug 16, 2018 10:02:21 AM org.apache.catalina.startup.Catalina start INFO: Server startup in 28819 ms Aug 16, 2018 10:02:23 AM org.apache.catalina.loader.WebappClassLoaderBase loadClass INFO: Illegal access: this web application instance has been stopped already. Could not load com.alibaba.rocketmq.shade.io.netty.util.concurren t.DefaultPromise$2. The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate t he thread which caused the illegal access, and has no functional impact. java.lang.IllegalStateException at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1743) at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1701) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:590) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.setSuccess(DefaultPromise.java:398) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:151) at java.lang.Thread.run(Thread.java:745) Exception in thread "NettyClientWorkerThread_3" java.lang.NoClassDefFoundError: com/alibaba/rocketmq/shade/io/netty/util/concurrent/DefaultPromi se$2 at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:590) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.setSuccess(DefaultPromise.java:398) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:151) at java.lang.Thread.run(Thread.java:745) Caused by: java.lang.ClassNotFoundException: com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise$2 at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1858) at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1701) ... 4 more Aug 16, 2018 10:02:23 AM org.apache.catalina.loader.WebappClassLoaderBase loadClass INFO: Illegal access: this web application instance has been stopped already. Could not load com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise$2. The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact. java.lang.IllegalStateException at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1743) at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1701) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:590) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.setSuccess(DefaultPromise.java:398) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:151) at java.lang.Thread.run(Thread.java:745) Exception in thread "NettyClientWorkerThread_4" java.lang.NoClassDefFoundError: com/alibaba/rocketmq/shade/io/netty/util/concurrent/DefaultPromise$2 at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:590) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.setSuccess(DefaultPromise.java:398) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:151) at java.lang.Thread.run(Thread.java:745) Exception in thread "NettyClientWorkerThread_2" Exception in thread "NettyClientWorkerThread_1" java.lang.NoClassDefFoundError: com/alibaba/rocketmq/shade/io/netty/util/concurrent/DefaultPromise$2 at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:590) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.setSuccess(DefaultPromise.java:398) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:151) at java.lang.Thread.run(Thread.java:745) java.lang.NoClassDefFoundError: com/alibaba/rocketmq/shade/io/netty/util/concurrent/DefaultPromise$2 at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:590) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.setSuccess(DefaultPromise.java:398) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:151) at java.lang.Thread.run(Thread.java:745) Exception in thread "NettyClientSelector_1" java.lang.NoClassDefFoundError: com/alibaba/rocketmq/shade/io/netty/util/concurrent/DefaultPromise$2 at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:590) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.DefaultPromise.setSuccess(DefaultPromise.java:398) at com.alibaba.rocketmq.shade.io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:151) at java.lang.Thread.run(Thread.java:745) Aug 16, 2018 10:02:28 AM org.apache.catalina.loader.WebappClassLoaderBase loadClass 很常见的一个错,java.lang.NoClassDefFoundError,找不到 com/alibaba/rocketmq/shade/io/netty/util/concurrent/DefaultPromise,但是不应该啊,因为什么都没有修改,之前都是跑的好好地,怎么会突然找不到这个类,莫名其妙,有同事说,是不是修改 jar 的版本之类的,不过我确实没有修改,只是引入了公司 ELK 相关的 jar 而已,而且看了一下这个类在 classpath 下是存在的,后来经同事提醒,这个地方可能不是真正的报错的地方,看一下 tomcat 的 localhost 日志,果然在发现了如下的报错: ...

August 17, 2018 · 2 分钟 · Bridge Li

关于 MySQL 中 utf8 的问题

有一个段子,我每次看了都头皮发麻,这个段子是: 手持两把锟斤拷,口中疾呼烫烫烫,脚踏千朵屯屯屯,笑看万物锘锘锘 我相信凡是和我有一样感觉的应该都知道这是啥,所以为了避免这个问题,我们工作的建议就是,所有的地方都设置成:UTF-8,确实我们这样工作了很多年,不知道哪一年 emoji 突然火了,于是在上家公司做论坛的时候,有一个小伙伴遇到了类似于下面的一个小问题(我找不到原报错日志了): Incorrect string value: ‘\xF0\x9F\x98\x83 <…’ for column ‘summary’ at row 1 当时问我,我也不知道原因,然后经过一番搜索之后,发现原来是 MySQL 存储 emoji 时出错了,存储 emoji 的时候,不应该用 utf8,而是用 utf8mb4。 随着移动互联网的到来,emoji 的应用越来越广泛,本以为这个问题大家都知道了,所以就没有深究过,想着自己记着就行了,但是不知道为什么最近这个问题突然火了,原来不是所有人都知道这个问题,甚至有人说,在做微信开发的时候,天知道微信用户会取什么昵称,所以不得已把用户的昵称都 base64 编码一下,展示的时候再解码,我只能表示:厉害,人民群众的智慧是无穷的。 其实产生这个问题的原因也很简单,据说是 MySQL 的一个 bug: MySQL 的 utf8mb4 是真正的utf8。 MySQL 的 utf8 是一种专属的编码,它能够编码的 Unicode 字符并不多。 关于什么是编码,我相信大家应该都知道了,如果不知道请自行搜索(当年曾经给一个计算机在读博的学姐科普什么是 GBK、GB2312、GB18030、UTF-8、BIG5、ISO-8859-1 等等,也是心累),至于为什么 MySQL 的 utf8 不是真正的 utf8,这是一个历史原因,我搜索的资料是:MySQL 从 4.1 版本开始支持 UTF-8,也就是 2003 年,而今天使用的 UTF-8 标准(RFC 3629)是随后才出现的。 所以现在我们看到的很多 MySQL(或者 MySQL 的分支:MariaDB)资料依然在建议,MySQL 的编码使用 utf8,这在绝大多数的情况下,确实也没有问题,但确实是错误的,最好的选择是,选用真正的 utf8,也就是:utf8mb4。对于已经选用 utf8 需要改用 utf8mb4 的项目,这有一个指南: ...

August 4, 2018 · 1 分钟 · Bridge Li

JVM 群关于 Autowired 的讨论

前一段时间 JVM 群有人遇到了一个 stackoverflow 的问题,引发了一个关于 Autowired 的讨论,由于我做的项目可能比较小,并没有遇到过,但感觉这也许就是一个坑,记录下来 ,如果谁有遇到这个问题,说不定就有帮助。 下面我会贴出来群里面的讨论,如果不想看,直接看我的得出的结论,所以 TL;DR 版: spring 中依赖注入有两个注解:Autowired 和 Resource。Resource 的注入的时候是 byName,而 Autowired 注入的时候是 byType,所以平时并没有很大的区别,但 Autowired 和 getBean(Object.class) 会导致栈很深,有可能会导致 stackoverflow,所以如果遇到这个栈很深,不使用 Autowired 会缓解一下,也就是根据我的理解,在项目中能用 Resource 还是用 Resource 比较好。 原版群里的详细讨论如下图(至于深层次的原因,大家可以根据源码自己跟进看一下): 里面 深圳-随缘-颉 贴出的一张图: 感谢这一群小伙伴,如果有信息泄漏,很抱歉。 另外曾经有朋友因为 JVM 的存在,就认为 Java 没有内存泄漏的问题,这是对 Java 有多大的误解啊。

July 29, 2018 · 1 分钟 · Bridge Li

Markdown 基本语法介绍

Markdown 是我很喜欢的一个轻量级标记语言,但也因为不常写,所以有些语法记得不是很清楚,经常写的时候需要查一些资料,所以这次就把一些简单的常用的语法做个笔记。 在介绍 markdown 语法之前,先写一点废话。 一. markdown是什么? 简单的一句话就是,Markdown 可谓是程序员必备的一种写作格式!你还在用 word 写文档么?简直 low 爆了,赶紧抛弃,从现在开始,立刻,马上,学习下 markdown。 二. markdown有什么好处? 简单说,语法简单、再也不用专注排版、兼容 html、还有其他的的很多很强大的功能(这么厉害,你的博客为啥不用?因为 WordPress 原生编辑器不支持 markdown,也一直没有安装第三方的插件,所以就没有) 三. markdown 编辑器有哪些? 个人最喜欢的开发工具是,GitHub 出品的 Atom,自带提示;之前也在 Windows 下用过 markdownpad,也还行,mac 下有很多人推荐 mou,另外据说 Sublime 也不错,还有一些在线工具据说也不错。 四. markdown 语法 标题 1 到 6:#1 到 #6 代码示例: # 标题一 ### 标题三 区块:> 代码示例: > 这是区块 > > 第二行区块 斜体:斜体 代码示例: *斜体* 加粗:加粗 代码示例: **该部分加粗** 删除线:删除线 示例代码: ~~这是加删除线的文字~~ 换行和分段 换行:只需在行末加两个空格键和一个回车键即可换行。快捷键:control + 回车键 分段:段落之间空一行即可 无序列表:*、+、- 代码示例: ...

July 15, 2018 · 1 分钟 · Bridge Li

Git 配置多个用户身份和强制检查各个项目用户名邮箱设置

今天的文章比较简单,1. 就是为 Git 单个项目做身份配置,就是配置单独的邮箱和用户名。因为我们平时可能会在不同的几个项目中工作,各个项目的用户名可能不同,最基本的就是公司的项目和我们自己在 GitHub 上玩,所以为了保证日志的准确性和提交时无误,最好对各个项目设置。以前没有研究过,所以就一只默认用公司的用户名玩,但一直感觉不太好,2. 在提交时,user.name, user.email会进入日志。这些信息,是追踪代码变更的关键,所以必须配置,偶然看见秋大有篇文章写这个,试了一些不错,记录一下。 全局配置和项目配置 全局配置信息在: ~/.gitconfig 项目配置在项目目录下的: ./.git/config 配置多个用户身份 git config –global 操作全局配置, 不带 –global 选项的话,会尝试相对于当前目录下找:./git/config, 找不到的话,报错,所以如此一来,我们就可以: # for global setting git config -global user.name xxx git config -global user.email xxx@xxx.com # for repository git config user.name xxxx git config user.email xxxx@xxx.com 另,我们都知道 Linux 下,我们可以通过 alias 命令设置别名(之前一直就不住这个命令,每次都是查一下,后来一朋友说多好记啊 ali as,阿里 as,就再也忘不了了),我们可以通过一个短命令把 git 日志重新格式化一下,至于其他的像:git status,git commit 是否重命名就看习惯了(看过有人把常错的单词都重命名为正确的,省时间,机智) git config -global alias.lg "log -color -graph -pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' -abbrev-commit -" 强制检查各个项目用户名邮箱设置 使用 pre-commit 钩子进行检查,所以在 .git/hooks 中添加一个 pre-commit 文件(注意权限),内容如下: ...

July 1, 2018 · 1 分钟 · Bridge Li

org.apache.ibatis.builder.IncompleteElementException: Could not find parameter map

上周和同事一块开发一个功能模块,在开发中拉下来同事代码,在测试的时候,突然跑不通了,报错信息如下: org.apache.ibatis.builder.IncompleteElementException: Could not find parameter map java.util.Map at org.apache.ibatis.builder.MapperBuilderAssistant.getStatementParameterMap(MapperBuilderAssistant.java:320) at org.apache.ibatis.builder.MapperBuilderAssistant.addMappedStatement(MapperBuilderAssistant.java:296) at org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode(XMLStatementBuilder.java:109) at org.apache.ibatis.session.Configuration.buildAllStatements(Configuration.java:753) at org.apache.ibatis.session.Configuration.hasStatement(Configuration.java:723) at org.apache.ibatis.session.Configuration.hasStatement(Configuration.java:718) at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:201) at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:48) at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:59) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:52) at com.sun.proxy.$Proxy40.insertSelective(Unknown Source) at com.weidai.urge.service.disposition.BidDispositionOrderServiceTest.testInsertSelective(BidDispositionOrderServiceTest.java:48) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: java.lang.IllegalArgumentException: Parameter Maps collection does not contain value for java.util.Map at org.apache.ibatis.session.Configuration$StrictMap.get(Configuration.java:853) at org.apache.ibatis.session.Configuration.getParameterMap(Configuration.java:625) at org.apache.ibatis.builder.MapperBuilderAssistant.getStatementParameterMap(MapperBuilderAssistant.java:318) ... 39 more 看报错信息,也就是自己跑一个 junit test,测试批量插入,突然他就不行了,再看看自己写的批量插入没问题啊,我也没用 map,感觉好奇怪,就网上搜了一下这个问题,原来确实有问题,也确实在这个配置文件中,提示的错误也对,就是提示的位置不对。mybatis 中只要有任何一个地方报错,都无法通过。最后搜了一下发现是刚刚来下来的代码,同事新增了一个方法上将 parameterType 写成了 parameterMap 了 ...

June 2, 2018 · 1 分钟 · Bridge Li

上传 Java 库到 Maven central repository

之前看过 Trinea 写过一篇文章,如果上传 Java 库到 Maven central repository,前一段时间感觉公司封装的 mybatis-generator 不好用,完全没有解决原生的 mybatis-generator 的问题,所以就重新做了一次封装,主要是加了查询分页,然后就想到是不是可以上传到 Maven central repository 玩玩,看了一下 Trinea 的这篇文章感觉挺简单的(原文见后面参考资料),但实际上还是有一些坑,具体的可以看 Trinea 的这篇文章,我主要写一下遇到的一些坑。 先说明一下,pom 文件请参考我的配置:https://github.com/bridgeli/mybatis-generator-plugin/blob/master/pom.xml,这里面所有的配置都是必须的,也是最少的了吧,自己写的时候修改一些内容,适配自己的就好了,Trinea 写的那个 pom 还是比较适合 Android。 发布 release 版本的时候,使用命令 mvn release:clean release:prepare release:perform 执行这个命令的时候,可能会遇到三个问题。 Git 报错,使用 Git 几年了,进入会遇到这样的错误。错误信息: Unable to commit files [ERROR] Provider message: [ERROR] The git-add command failed. [ERROR] Command output: 实在不知道咋回事,可能是我有些文件没提交,反正提交之后就好了,看到有人说他报错是因为他不是 Git 管理的项目,我作为 Git 脑残粉,肯定是的,可能就是因为没有提交吧,反正最后就这么莫名其妙的解决了。 提示没有 snapshot 版本,所以执行这个命令的时候,版本号和发布 snapshot 一样就好,而不是自己手动改成 release 版。 因为此时会发布一个 release 版本,会有 tag 推送到 GitHub 有可能会遇到 401,看到这个 error code,应该都清楚咋回事,没权限。不知道,病急乱投医,我是在 GitHub 上添加了一个 GPG key 并且在 pom 文件中新增一个 plugin。这个问题有人说是账号和密码错误,但是我很肯定我的账号和密码一点也没有错。 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-gpg-plugin</artifactId> <version>1.5</version> <executions> <execution> <id>sign-artifacts</id> <phase>verify</phase> <goals> <goal>sign</goal> </goals> </execution> </executions> </plugin> 最终解决了这个问题,关于这个问题,可能是我操作的问题没有看到过别人说过,另外在 GitHub 上他的颜色一直还是:灰的,没有显示使用过,所以可能是我操作的有问题,不需要在 GitHub 上添加这个 GPG key,具体大家可以测一下,我后期也会测试。 另,需要说明的是,生成 GPG key 的时候用到的邮箱和我在 GitHub 上的邮箱,我用的不是同一个邮箱,建议还是用同一个吧,省得出莫名其妙的问题。 ...

May 20, 2018 · 1 分钟 · Bridge Li

Java Thread 同步

之前遇到一个问题,就是如何让线程同步,由于自己多线程的东西实在不懂,所以不知道怎么办,但感觉应该是一个很简单的东西,所以就从网上搜一下资料,原来如此简单,直接调用 join 方法就好了。写篇博客记录一下 join 的使用方法。 作用 Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。具体看代码: package cn.bridgeli.demo; public class ThreadTest { public static void main(String[] args) throws InterruptedException { ThreadJoinTest t1 = new ThreadJoinTest("bridgeli"); ThreadJoinTest t2 = new ThreadJoinTest("liqiao"); t1.start(); /** * join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是: * 程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕 * 所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会 * join方法可以传递参数,join(10000)表示main线程会等待t1线程10毫秒,10毫秒过去后, * main线程和t1线程之间执行顺序由串行执行变为普通的并行执行 */ t1.join(10000); t2.start(); } } package cn.bridgeli.demo; public class ThreadJoinTest extends Thread { public ThreadJoinTest(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(this.getName() + ":" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } 上面注释也大概说明了 join 方法的作用:在 主线程 中调用了 t1 线程的 join() 方法时,表示只有当 t1 线程执行完毕时,主线程 才能继续执行,也就是开始执行 t2。注意,join 方法其实也可以传递一个参数给它的,表示:如果 主线程 在 t1 执行 1000 毫秒之后,继续执行,也就是开启 t2 线程。 ...

May 12, 2018 · 3 分钟 · Bridge Li

介绍一个 Mybatis 插件:mybatis-generator-plugin

在实际开发中,我们都是先建表,然后根据表生成对应的 Java 类,现在很流行的 ORMapping 框架是: Mybatis,所以我们需要生成 entity、mapper 和 xml,我们都知道有一个插件是:mybatis-generator,使用它就可以很方便的生成这些结构化的重复性基础性的代码,但是他有一个问题,生成的查询没有分页,所以很烦。然后我搜索了一些资料,重新封装了一下,重新命名为:mybatis-generator-plugin,具体源码放在了 GitHub 上: https://github.com/bridgeli/mybatis-generator-plugin 大家可以 clone 下来,deploy 到自己公司的私服,或者 install 到本地使用。具体就是在 pom 文件中。具体用法 在 pom 文件中添加如下内容: <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.5</version> <dependencies> <dependency> <groupId>cn.bridgeli</groupId> <artifactId>mybatis-generator-plugin</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <configuration> <verbose>true</verbose> <overwrite>true</overwrite> <configurationFile>src/main/resources/generatorConfig.xml</configurationFile> </configuration> </plugin> </plugins> </build> 其中:configurationFile 配置 generatorConfig.xml 文件的位置,我们生成的对应的文件,都是根据这个文件来的。所以第二步就是: 在对应的文件下添加该文件,它的模板如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- 请替换本地jar路径 此文件不要上传 --> <classPathEntry location="/Users/bridgeli/.m2/repository/mysql/mysql-connector-java/5.1.33/mysql-connector-java-5.1.33.jar" /> <!-- mvn mybatis-generator:generate --> <context id="oneHour" targetRuntime="cn.bridgeli.plugin.IntrospectedTableMyBatis3ImplExt"> <property name="suppressAllComments" value="true" /> <property name="useActualColumnNames" value="false" /> <!-- 格式化java代码 --> <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter" /> <!-- 格式化XML代码 --> <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter" /> <!-- 配置插件 --> <plugin type="cn.bridgeli.mybatis.MySQLLimitPlugin"></plugin> <commentGenerator> <!-- 是否去除自动生成的注释 true:是 : false:否 --> <property name="suppressAllComments" value="true" /> </commentGenerator> <!--数据库连接的信息:驱动类、连接地址、用户名、密码 --> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://ip:port/databasename" userId="username" password="password"> </jdbcConnection> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!-- 配置model生成位置 --> <javaModelGenerator targetPackage="cn.bridgeli.entity" targetProject="src/main/java"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- 配置sqlmap生成位置 --> <sqlMapGenerator targetPackage="cn/bridgeli/mapper" targetProject="src/main/resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!-- 配置mapper接口生成位置 --> <javaClientGenerator type="XMLMAPPER" targetPackage="cn.bridgeli.mapper" targetProject="src/main/java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!-- 配置表信息 --> <table tableName="table_name" enableCountByExample="true" domainObjectName="DomainObjectName" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="true"> <generatedKey column="ID" sqlStatement="MySQL" identity="true" /> </table> </context> </generatorConfiguration> 这里面需要说明就是: ...

April 29, 2018 · 2 分钟 · Bridge Li

介绍一个强大易用的日期和时间库:Joda-Time

在 Java 中处理日期和时间是很常见的需求,基础的工具类就是我们熟悉的 Date 和 Calendar,之前我也曾经写过一篇文章利用这两个类,怎么处理时间,然而这些工具类的 api 使用并不是很方便和强大,于是就诞生了Joda-Time 这个专门处理日期时间的库。而且 Joda-Time 很优秀,用了他之后再也停不下来,其在 Java 8 出现前的很长时间内成为 Java 中日期时间处理的事实标准,用来弥补 JDK 的不足。项目中要想使用 Joda-Time 很简单,只需要引入依赖: <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.9.9</version> </dependency> 下面说一些常用的例子,供平时参考 创建任意时间对象 DateTime dateTime=new DateTime(2018, 03, 31, 16, 57,55); 创建当前时间对象 DateTime dateTime=new DateTime(); 格式化时间输出 DateTime dateTime = new DateTime(); System.out.println(dateTime.toString("yyyy-MM-dd HH:mm:ss")); 解析文本格式时间 DateTimeFormatter format = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); DateTime dateTime = DateTime.parse("2018-03-31 17:01:45", format); 在当前日期上加上 90 天并输出结果 DateTime dateTime = new DateTime(); System.out.println(dateTime.plusDays(90).toString("yyyy-MM-dd HH:mm:ss"); 获取今天的开始时间 DateTime nowTime = new DateTime(); DateTime startOfDay = nowTime.withTimeAtStartOfDay(); 获取今天的结束时间 DateTime nowTime = new DateTime(); DateTime endOfDay = nowTime.millisOfDay().withMaximumValue(); 计算两个日期的相隔天数 DateTime nowTime = new DateTime(); DateTime futureTime = new DateTime(2019, 10, 1, 0, 0, 0); Int days = Days.daysBetween(nowTime, futureTime).getDays(); 本月的第一天和最后一天 DateTime dateTime = new DateTime(); DateTime firstDateTimeOfMonth = dateTime.dayOfMonth().withMinimumValue().withTimeAtStartOfDay(); DateTime lastDateTimeOfMonth = dateTime.dayOfMonth().withMaximumValue().withTimeAtStartOfDay(); 上个月的第一天和最后一天 DateTime dateTime = new DateTime(); DateTime startDate = dateTime.plusMonths(-1).dayOfMonth().withMinimumValue().withTimeAtStartOfDay(); DateTime endDate = dateTime.plusMonths(-1).dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue(); 在每天的6:30处理一些东西 DateTime dt=new DateTime().withHourOfDay(6).withMinuteOfHour(30).withSecondOfMinute(0); 在每月的7号的6:30处理一些东西 DateTime dt=new DateTime().withDayOfMonth(7).withHourOfDay(6).withMinuteOfHour(30).withSecondOfMinute(0); 在每年的8月的7号的6:30处理一些东西 DateTime dt=new DateTime().withMonthOfYear(8).withDayOfMonth(7).withHourOfDay(6).withMinuteOfHour(30).withSecondOfMinute(0); 获取年终 月、日、时、分、秒、毫秒 DateTime dt = new DateTime(); //获取当前时间的年 int year = dt.getYear(); //获取当前时间的月 int month = dt.getMonthOfYear(); //获取当前时间是一年中的第几天 int dayOfYear = dt.getDayOfYear(); //获取一个月中的天 int day = dt.getDayOfMonth(); //获取一周中的周几 int week = dt.getDayOfWeek(); //一天中的第几小时(取整) int hour = dt.getHourOfDay(); //获取星期年 int weekOfyear = dt.getWeekyear(); //当前时间的秒中的毫秒 int ms = dt.getMillisOfSecond(); //获取当前时间的秒 int second = dt.getSecondOfDay(); //获取当前时间的毫秒 long millis = dt.getMillis(); 判断时间跨度是否包含当前时间,某个时间 Interval interval = new Interval(new DateTime(2018, 1, 1, 0, 0, 0), new DateTime(2018, 3, 30, 0, 0, 0)); System.out.println(interval.containsNow()); boolean contained = interval.contains(new DateTime("2012-03-01")); 与JDK互转换 //通过jdk时间对象构造 Date date = new Date(); DateTime dateTime = new DateTime(date); Calendar calendar = Calendar.getInstance(); dateTime = new DateTime(calendar); // Joda-time 各种操作..... dateTime = dateTime.plusDays(1) // 增加天 .plusYears(1)// 增加年 .plusMonths(1)// 增加月 .plusWeeks(1)// 增加星期 .minusMillis(1)// 减分钟 .minusHours(1)// 减小时 .minusSeconds(1);// 减秒数 // 计算完转换成jdk 对象 Date date2 = dateTime.toDate(); Calendar calendar2 = dateTime.toCalendar(Locale.CHINA); 解析 CST 格式的时间字符串 DateTimeFormatter format = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss zzz yyyy"); DateTime dateTime = DateTime.parse("Fri Mar 30 00:00:00 CST 2018", format); 当然作为一个强大的 时间日期 处理工具类,他还有更多更强大的 API,可以参看: ...

March 31, 2018 · 2 分钟 · Bridge Li