Android Bug记 'Canvas: trying to use a recycled bitmap android.graphics.Bitmap'

Bug日志

最近一个项目中遇到一个诡异Bug,详细日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
E/MainActivity: executeTextView: test for get drawable:  last source: android.graphics.drawable.BitmapDrawable@8c352b2
executeTextView: test for get drawable: isVisible true alpha: 255 last source: android.graphics.drawable.BitmapDrawable@8c352b2
W/Bitmap: Called hasAlpha() on a recycle()'d bitmap! This is undefined behavior!
Called hasAlpha() on a recycle()'d bitmap! This is undefined behavior!
Called hasAlpha() on a recycle()'d bitmap! This is undefined behavior!
W/Bitmap: Called hasAlpha() on a recycle()'d bitmap! This is undefined behavior!
E/MainActivity: Glide结束
executeImageView: ...
E/MainActivity: showQrCode: 举报二维码1:
showQrCode: 举报二维码2: https://xxx.com/upload/equipmentWxQRCode/15776698271ada7952f9ead4d5.jpg
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.rootrl.adviewer, PID: 29128
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@ac257b9
at android.graphics.Canvas.throwIfCannotDraw(Canvas.java:1271)
at android.view.DisplayListCanvas.throwIfCannotDraw(DisplayListCanvas.java:257)
at android.graphics.Canvas.drawBitmap(Canvas.java:1415)
at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:528)
at android.widget.ImageView.onDraw(ImageView.java:1298)
at android.view.View.draw(View.java:17201)
at android.view.View.updateDisplayListIfDirty(View.java:16183)
at android.view.View.draw(View.java:16967)
at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
at androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw(ConstraintLayout.java:2023)
at android.view.View.updateDisplayListIfDirty(View.java:16178)
at android.view.View.draw(View.java:16967)
at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
at androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw(ConstraintLayout.java:2023)
at android.view.View.draw(View.java:17204)
at android.view.View.updateDisplayListIfDirty(View.java:16183)
at android.view.View.draw(View.java:16967)
at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
at android.view.View.updateDisplayListIfDirty(View.java:16178)
at android.view.View.draw(View.java:16967)
at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
at android.view.View.updateDisplayListIfDirty(View.java:16178)
at android.view.View.draw(View.java:16967)
at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
at android.view.View.updateDisplayListIfDirty(View.java:16178)
at android.view.View.draw(View.java:16967)
at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
at android.view.View.updateDisplayListIfDirty(View.java:16178)
at android.view.View.draw(View.java:16967)
at android.view.ViewGroup.drawChild(ViewGroup.java:3727)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)
at android.view.View.draw(View.java:17204)
at com.android.internal.policy.DecorView.draw(DecorView.java:754)
at android.view.View.updateDisplayListIfDirty(View.java:16183)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:648)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:654)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:762)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:2800)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2608)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2215)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1254)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6338)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:874)
at android.view.Choreographer.doCallbacks(Choreographer.java:686)
at android.view.Choreographer.doFrame(Choreographer.java:621)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:860)
at android.os.Handler.handleCallback(Handler.java:755)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6121)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:905)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:795)
I/Process: Sending signal. PID: 29128 SIG: 9
Process 29128 terminated.

Bug初步分析

其实字面上看上去很简单。但是诡异在发生场景:

  • 只在安卓横屏模式下发生,竖屏模式下正常。

  • 有一张特定图片才会出现问题,其他图片均不会。而这个图片无论分辨率大小还是文件大小均不大,其他比它大十几倍的都正常运行。

报错原因从日志上看到很简单:使用了一个已经被回收的bitmap资源(我这里使用的是Glide图片处理库)。但是结合我的使用场景和发生场景(只在横屏下),再加上Glide对于我来说是一个黑箱。 种种原因结合看来是一个难调的bug。

后来发现发生的地方是imageView的Placeholder设置阶段。代码如下:

1
2
3
4
5
if (currentView == AdConstant.VIEW_TYPE_TEXT_VIEW) {
if (adImageView.getDrawable() != null) {
requestOptions.placeholder(adImageView.getDrawable());
}
}

设置这个Placeholder是为了解决图片切换时的闪黑屏问题,一是去掉Glide的Animate,二是设置这个Placeholder,把当前Image View的Drawable作为默认图片。而由于我的业务逻辑复杂,有图片和视频的轮播,有可能在设置时找不到这个Drawable的Bitmap资源,好吧,说有可能是因为我也不能给个具体的原因-_-‘’,因为结合我上面提到的两个特定发生场景,实在是太诡异了。

Bug深入分析

后来我看到github上官方bumptech/glide也有一大堆issues,有人说是glide版本问题,但是我更新到最新的4.10.0依旧无解。

最后看到官方的Common errors文档,http://bumptech.github.io/glide/doc/resourcereuse.html#common-errors

1
2
3
4
5
6
7
8
9
10
11
12
Glide’s BitmapPool has a fixed size. When Bitmaps are evicted from the pool without being re-used, Glide will call recycle(). If an application inadvertently continues to hold on to the Bitmap even after indicating to Glide that it is safe to recycle it, the application may then attempt to draw the Bitmap, resulting in a crash in onDraw().

This problem could be due to the fact that one target is being used for two ImageViews, and one of the ImageViews still tries to access the recycled Bitmap after it has been put into the BitmapPool. This recycling error can be hard to reproduce, due to several factors: 1) when the bitmap is put into the pool, 2) when the bitmap is recycled, and 3) what the size of the BitmapPool and memory cache are that leads to the recycling of the Bitmap. The following snippet can be put into your GlideModule to help making this problem easier to reproduce:

@Override
public void applyOptions(Context context, GlideBuilder builder) {
int bitmapPoolSizeBytes = 1024 * 1024 * 0; // 0mb
int memoryCacheSizeBytes = 1024 * 1024 * 0; // 0mb
builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
builder.setBitmapPool(new LruBitmapPool(bitmapPoolSizeBytes));
}
The above code makes sure that there is no memory caching and the size of the BitmapPool is zero; so Bitmap, if happened to be not used, will be recycled right away. The problem will surface much quicker for debugging purposes.

第一段说明了真正原因,Bitmap在BitmapPool中被剔除而没有被重用时,Glide会调用recycle(),但是如果Application在被告知安全回收了Bitmap之后还是保留这个Bitmap,继而绘制Bitmap时,在onDraw中就会崩溃。

我这个Placeholder就发生在这种情况下。

Bug解决

我这边解决思路是重新设置BitmapPool的大小,这需要重写AppGlideModule,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.rootrl.adviewer.glide;

import android.content.Context;

import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;
import com.bumptech.glide.load.engine.cache.LruResourceCache;
import com.bumptech.glide.module.AppGlideModule;

@GlideModule
public class AdImageGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
int bitmapPoolSizeBytes = 1024 * 1024 * 200; // 200mb
int memoryCacheSizeBytes = 1024 * 1024 * 200; // 200mb
builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
builder.setBitmapPool(new LruBitmapPool(bitmapPoolSizeBytes));
}
}

这里有几点要注意,不然项目中没有GlideApp对象。

  • 类中添加@GlideModule注解
  • 如同package com.rootrl.adviewer.glide,这个Module放在项目路径的glide package目录(需新建)
  • 改下build.grdle配置

其中第三条具体如下,注意除了glide依赖,还需annotationProcessor项:

1
2
implementation 'com.github.bumptech.glide:glide:4.10.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'

然后,点AS的Build => Make Project,之后就可以在项目中使用集成自己GlideModule的GlideAPP了。

使用方式也是用GlideAPP替换原来的Glide就可以。

1
2
3
4
5
// 替换前
Glide.with(MainActivity.this).listener(...).load(uri).apply(requestOptions).into(adImageView);

// 替换后
GlideApp.with(MainActivity.this).listener(...).load(uri).apply(requestOptions).into(adImageView);

总结

其实这里还没有具体深入,因为安卓对我来说还是一个实用为主阶段。最后强调是图片处理库非常推荐Glide,它的缓存机制很实用。然后视频的缓存推荐danikula:videocache库。

Share Comments

FastDFS Docker化部署 以及 Java SpringMVC实践

FastDFS Docker化部署 以及 Java SpringMVC实践

简介

FastDFS是一个轻量级分布式文件系统。可以对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,而且可以集群部署,有高可用保障。相应的竞品有Ceph、TFS等。相比而言FastDFS对硬件的要求比较低,所以适合中小型公司。

概念

FastDFS服务端由两个重要部分组成:跟踪器(Tracker)和存储节点(Storage)。

Tracker主要做调度工作,在访问上起负载均衡的作用。Tracker可以做集群部署,各个节点之间是平等的,客户端请求时采用轮询机制,某个Tracker不能提供服务时就换另一个。Storage启动后会连接到Tracker Server告知自己的Group信息,形成映射关联,并采用心跳机制保持状态。
Storage存储节点负责文件的存储,Storage可以集群部署。

Storage集群有以下特点:

  • 以组(Group)为单位(也有称呼为卷 Volume的),集群的总容量为所有组的集合。
  • 一个卷(组)内storage server之间相互通信,文件进行同步,保证卷内storage完全一致,所以一个卷的容量以最小的服务器为准。不同的卷之间相互不通信。
  • 当某个卷的压力较大时可以添加storage server(纵向扩展),如果系统容量不够可以添加卷(横向扩展)。

上传流程

此章节根据资料整理,可能随着版本有所改变,这里只介绍大致的,以便了解整个运作流程。如果需要深入研究,建议还是以官方文档为标准。

一,客户端请求会打到负载均衡层,到tracker server时,由于每个server之间是对等的关系,所以可以任意选择一个tracker server。

二,到storage层:tracker server接收到upload file请求时,会为该请求分配一个可以存储该文件的group。

分配group规则:

  • Round robin 轮询
  • Specified group 指定一个group
  • Load balance 剩余存储空间多的group优先

三,确定group后,tracker会在group内选择一个storage server给客户端。

在group内选择storage server时规则:

  • Round robin 轮询
  • First server ordered by ip 按ip排序
  • First server ordered by priority,按优先级排序(优先级在storage上配置)

四,选择storage path:当分配好storage server后,客户端向storage发送写文件请求,storage将会为文件分配一个数据存储目录,支持规则如下:

  • round robin 轮询
  • 剩余存储空间最多的优先

五,生成File id:选定存储目录之后,storage会为文件生成一个File id。规则如下:
由storage server ip、文件创建时间、文件大小,文件crc32和一个随机数拼接而成,然后将这个二进制串进程base64编码,转换为可打印的字符串。

六,选择两级目录:每个存储目录下有两级256 * 256的子目录,storage会按文件Field进行两次hash,路由到其中的一个目录,然后将文件以file id为文件名存储到该子目录下。

一个文件路径最终由如下组成:组名/磁盘/目录/文件名

七,客户端upload file成功后,会拿到一个storage生成的文件名,接下来客户端根据这个文件名即可访问到该文件。

下载流程

下载流程如下:

一,选择tracker server:和upload file一样,在download file时随机选择tracker server。

二,选择group:tracker发送download请求给某个tracker,必须带上文件名信息,tracker从文件名中解析出group、大小、创建时间等信息,根据group信息获取对于的group。

三,选择storage server:从group中选择一个storage用来服务读请求。由于group内的文件同步时在后台异步进行的,所以有可能出现在读到的时候,文件还没有同步到某些storage server上,为了尽量避免反问道这样的storage,tracker按照一定的规则选择group内可读的storage。

文件HTTP预览服务

Storage还可以结合nginx的fastdfs-nginx-module提供http服务,以实现图片等预览功能。

这个部分这里不做介绍,后续可能单独写篇文章,因为我发现对fastDFS集群提供http服务还是挺复杂,包括我下面找的docker镜像都不完善,主要是规划的问题,包括衍生的服务,缓存,以及对图片的处理(nginx+lua)这些,后续打算研究下,重新开源个docker构建镜像。

实战

安装、部署规划

FastDFS安装方法网上有很多教程,这里不多讲,我建议使用docker来运行FastDFS,可以自己根据安装步骤构建自己的镜像。然后在需要的机器直接运行,后续扩容也方便,再启动一个storage容器就可以了。

详细版安装推荐篇文章:https://segmentfault.com/a/1190000008674582

Docker集群搭建

我这里从github上找的一个别人构建好的镜像,可以直接使用。地址:https://github.com/luhuiguo/fastdfs-docker

使用方法也很简单

1
2
3
4
5
6
7
8
9
10
11
12

# 启动一个tracker服务器
docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs luhuiguo/fastdfs tracker

# 启动storage0
docker run -dti --network=host --name storage0 -e TRACKER_SERVER=10.1.5.85:22122 -v /var/fdfs/storage0:/var/fdfs luhuiguo/fastdfs storage

# 再启动一个storage1
docker run -dti --network=host --name storage1 -e TRACKER_SERVER=10.1.5.85:22122 -v /var/fdfs/storage1:/var/fdfs luhuiguo/fastdfs storage

# 启动一个新组的storage
docker run -dti --network=host --name storage2 -e TRACKER_SERVER=10.1.5.85:22122 -e GROUP_NAME=group2 -e PORT=22222 -v /var/fdfs/storage2:/var/fdfs luhuiguo/fastdfs storage

部署注意点

1,原github地址上的usage介绍,启动storage0和storage1有一个参数错误(多一个-e),以我上面发的命令为准。
2,这里的TRACKER_SERVER注意改为你自己的,同一个网段内网ip。

3,实际上这里docker容器之间还是同一个物理主机上部署的(根据network而言),虽然后续可以通过加硬盘,然后新建storage绑定到新加硬盘mount上,但是如果是大公司的生产环境还是推荐建立一个overlay网络,具体见:https://www.cnblogs.com/bigberg/p/8521542.html,这样可以直接扩物理机集群了。另外这里也提供docker-compose方式启动服务,实际也不推荐使用,因为tracker和storage server以后必然是分开的,所以还是推荐单个docker容器保持灵活性。这里高级点可以用k8s进行自动扩容(后续打算重新开源个镜像)。

Java实践

导入需要包

这里使用官方的客户端包:https://github.com/happyfish100/fastdfs-client-java

1
2
3
4
5
6
7
8
9
10
11
12
13
# 下载源码
git clone https://github.com/happyfish100/fastdfs-client-java.git

cd fastdfs-client-java

# 打jar包
mvn clean install

# 输出目录
cd target

# 导入到本地仓库 注意这里version根据实际生成的来
mvn install:install-file -DgroupId=org.csource -DartifactId=fastdfs-client-java -Dversion=1.27-SNAPSHOT -Dpackaging=jar -Dfile=fastdfs-client-java-1.27-SNAPSHOT.jar

在pom.xml中引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<dependency>
<groupId>org.csource</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>

<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.2</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>

添加Client配置

在resource目录下,添加conf/fdfs_client.conf配置文件

1
2
3
4
5
6
7
8
connect_timeout = 2
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 80
http.anti_steal_token = no
http.secret_key = FastDFS1234567890

tracker_server = 192.168.1.163:22122

测试时实际上只需关注tracker_server,并且改为你自己的tracker server

添加文件上传bean

applicationContext.xml配置中添加文件上传bean

1
2
3
4
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="62914560" />
<property name="defaultEncoding" value="UTF-8" />
</bean>

建一个Client封装

建一个简单的client封装(勿作生产使用)
FastDFSClient.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.rootrl.fastDFSDemo.utiles;

import org.apache.commons.lang3.StringUtils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

public class FastDFSClient {

private static StorageClient1 storageClient1 = null;

static {
try {
// 获取配置文件
String classPath = new File(FastDFSClient.class.getResource("/").getFile()).getCanonicalPath();
String CONF_FILENAME = classPath + File.separator + "conf" + File.separator + "fdfs_client.conf";
ClientGlobal.init(CONF_FILENAME);
// 获取触发器
TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group);
TrackerServer trackerServer = trackerClient.getConnection();
// 获取存储服务器
StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
storageClient1 = new StorageClient1(trackerServer, storageServer);
} catch (Exception e) {
System.out.println(e);
}
}

/**
* 上传文件
* @param fis 文件输入流
* @param fileName 文件名称
* @return
*/
public static String uploadFile(InputStream fis, String fileName) {
try {
NameValuePair[] meta_list = null;

//将输入流写入file_buff数组
byte[] file_buff = null;
if (fis != null) {
int len = fis.available();
file_buff = new byte[len];
fis.read(file_buff);
}

String fileid = storageClient1.upload_file1(file_buff, getFileExt(fileName), meta_list);
return fileid;
} catch (Exception ex) {
return null;
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
System.out.println(e);
}
}
}
}


/**
* 获取文件后缀
* @param fileName
* @return
*/
private static String getFileExt(String fileName) {
if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {
return "";
} else {
return fileName.substring(fileName.lastIndexOf(".") + 1);
}
}
}

建立控制器

然后建立一个File控制器,做测试用
FileController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.rootrl.fastDFSDemo.controller;

import com.rootrl.fastDFSDemo.utiles.FastDFSClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

@Controller
@RequestMapping("fastdfs")
public class FileController {

@RequestMapping(value = "upload")
@ResponseBody
public String uploadFileSample(@RequestParam MultipartFile file){

try {
String fileId = FastDFSClient.uploadFile(file.getInputStream(), file.getOriginalFilename());

return fileId;

} catch (Exception e) {
System.out.println(e.getMessage());
return "error";
}
}

}

然后使用postman客户端测试,url为:http://localhost:8080/fastdfs/upload.do(依据自己实际情况变更)

注意postman使用post请求,然后切换到body/form-data标签项,添加一个Key为file,类型为file,然后value就可以上传文件了。成功会返回文件id,类似:group1/M00/00/00/wKgBo1zjxnOAT-k1AAAoMlb3hzU996.png

参考

https://blog.csdn.net/yxflovegs2012/article/details/53868362

Share Comments

Intellij Idea 中进行 Mybatis逆向工程

开篇

Mybatis有个实用的功能就是逆向工程,能根据表结构反向生成实体类,这样能避免手工生成出错。市面上的教程大多都很老了,大部分都是针对mysql5的,以下为我执行mysql8时的经验。

引入工程

这里使用的是maven包管理工具,在pom.xml添加以下配置,以引入mybatis.generator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<build>
<finalName>SpringMVCBasic</finalName>
<!-- 添加mybatis-generator-maven-plugin插件 -->
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>

配置文件

在maven项目下的src/main/resources 目录下新建generatorConfig.xml和generator.properties文件

generator.properties

1
2
3
4
5
jdbc.driverLocation=F:\\maven-repository\\mysql\\mysql-connector-java\\8.0.16\\mysql-connector-java-8.0.16.jar
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.connectionURL=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8
jdbc.userId=test
jdbc.password=test123

注意:
1,generator.properties里面的jdbc.driverLocation指向是你本地maven库对应mysql-connector地址
2,与老版本不同,这里driversClass为com.mysql.cj.jdbc.Driver

generatorConfig.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

<?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>

<!--导入属性配置-->
<properties resource="generator.properties"></properties>

<!--指定特定数据库的jdbc驱动jar包的位置(绝对路径)-->
<classPathEntry location="${jdbc.driverLocation}"/>

<context id="default" targetRuntime="MyBatis3">

<!-- optional,旨在创建class时,对注释进行控制 -->
<commentGenerator>
<!--是否去掉自动生成的注释 true:是-->
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>

<!--jdbc的数据库连接:驱动类、链接地址、用户名、密码-->
<jdbcConnection
driverClass="${jdbc.driverClass}"
connectionURL="${jdbc.connectionURL}"
userId="${jdbc.userId}"
password="${jdbc.password}">
</jdbcConnection>

<!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>

<!-- Model模型生成器,用来生成含有主键key的类,记录类 以及查询Example类
targetPackage 指定生成的model生成所在的包名
targetProject 指定在该项目下所在的路径
-->
<javaModelGenerator targetPackage="com.ifly.outsourcing.entity"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>


<!--Mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 -->
<sqlMapGenerator targetPackage="mappers"
targetProject="src/main/resources">
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>

<!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码
type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象
type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象
type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口
-->

<javaClientGenerator type="XMLMAPPER" targetPackage="com.ifly.outsourcing.dao"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>

<!-- 数据表进行生成操作 tableName:表名; domainObjectName:对应的DO -->
<table tableName="user" domainObjectName="user"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>

</context>
</generatorConfiguration>

注意:这里主要注意修改对应的javaModelGenerator ,sqlMapGenerator,javaClientGenerator 为自己的生成路径。以及添加自己的数据表。

在Intellij IDEA添加一个“Run运行”选项

点击菜单栏的run,新建一个选项为maven的configurations,name为自己方便看,比如generator,commnd line注意写为:

1
mybatis-generator:generate -e

点击run即可生成对应文件。

Share Comments