MyBatis事务管理解析:颠覆你心中对事务的理解!
1 .说到数据库事务,人们脑海里自然不自然的就会浮现出事务的四大特性、四大隔离级别、七大传播特性。
四大还好说,问题是七大传播特性是哪儿来的?是Spring在当前线程内,处理多个数据库操作方法事务时所做的一种事务应用策 略。
事务本身并不存在什么传播特性,不要混淆事务本身和Spring的事务应用策略。(当然,找工作面试时,还是可以巧妙的描述传播 特性的)
2 .一说到事务,人们可能又会想起create、begin、commit、rollback、close、suspend。
可实际上,只有commit、rollback是实际存在的,剩下的create、begin、close、suspend都是虚幻的,是业务层或数据库底层 应用语意,而非JDBC事务的真实命令。
create (事务创建):不存在。
begin (事务开始):姑且认为存在于DB的命令行中,比如Mysql的start transaction命令,以及其他数据库中的begin transaction命令。JDBC中不存在。
close (事务关闭):不存在。应用程序接口中的close()方法,是为了把connection放回数据库连接池中,供下一次使用,与事务 毫无关系。
suspend (事务挂起):不存在。
Spring中事务挂起的含义是,需要新事务时,将现有的connection1保存起来(它还有尚未提交的事务),然后创建 connection?, connection2提交、回滚、关闭完毕后,再把connection1取出来,完成提交、回滚、关闭等动作,保存 connection1的动作称之为事务挂起。
在JDBC中,是根本不存在事务挂起的说法的,也不存在这样的接口方法。
因此,记住事务的三个真实存在的方法,不要被各种事务状态名词所迷惑,它们分别是:conn.setAutoCommit()、conn.commit()、 conn.rollback()。
conn.close()含义为关闭一个数据库连接,这已经不再是事务方法了。
1 . Mybaits中的事务接口Transaction
public interface Transaction {
ConnectiongetConnection() throwsSQLException ;
void commit() throwsSQLException ;
void rollback。throwsSQLException ;
void close() throwsSQLException ;
}
有了文章开头的分析,当你再次看到close()方法时,千万别再认为是关闭一个事务了,而是关闭一个conn连接,或者是把conn连 接放回连接池内。
事务类层次结构图:
JdbcTransaction:单独使用Mybatis时,默认的事务管理实现类,就和它的名字一样,它就是我们常说的JDBC事务的极简封装, 和编程使用mysql-connector-java-5.1.38-bin.jar事务驱动没啥差别。其极简封装,仅是让connection支持连接池而已。
ManagedTransaction:含义为托管事务,空壳事务管理器,皮包公司。仅是提醒用户,在其它环境中应用时,把事务托管给其它 框架,比如托管给Spring,让Spring去管理事务。
org.apache.ibatis.transaction.jdbc.JdbcTransaction.java部分源码。
@Override
public void close() throwsSQLException {
if(connection!= null){
resetAutoCommit();
if(log.isDebugEnabled()){
log.debug(“ClosingJDBCConnection[”+connection+“]”);
}
connection.close();
}
}
面对上面这段代码,我们不禁好奇,connection.close()之前,居然调用了一^个resetAutoCommit(),含义为重置autoCommit属 性值。
connection.close()含义为销毁conn,既然要销毁conn,为何还多此一举的调用一^个resetAutoCommit()呢?消失之前多喝口 水,真的没有必要。
其实,原因是这样的,connection.close()不意味着真的要销毁conn,而是要把conn放回连接池,供下一次使用,既然还要使 用,自然就需要重置AutoCommit属性了。
通过生成connection代理类,来实现重回连接池的功能。如果connection是普通的Connection实例,那么代码也是没有问题的, 双重支持。
2 .事务工厂TransactionFactory
。JdbcTransactionFactory O ManagedTransactionFactory
顾名思义,一个生产JdbcTransaction实例,一个生产ManagedTransaction实例。两个毫无实际意义的工厂类,除了“6川之外,没有其他代码。
<transactionmanagertype=“JDBC”/>
mybatis-config.xml配置文件内,可配置事务管理类型。
3 . Transaction的用法
无论是SqlSession,还是Executor,它们的事务方法,最终都指向了Transaction的事务方法,即都是由Transaction来完成事务 提交、回滚的。
配一个简单的时序图。
代码样例:
public static void main(String[]args){
SqlSessionsqlSession:MybatisSqlSessionFactory.openSession();
try{
StudentMapperstudentMapper:sqlSession.getMapper(StudentMapper.class);
Studentstudent: newStudent(); student.setName( “yy”);
student.setEmail( “email@email.com”);
student.setDob( newDate());
student.setPhone( newPhoneNumber(“123-2568-8947”));
studentMapper.insertStudent(student);
sqlSession.commit();
} catch(Exceptione){ sqlSession.rollback();
} finally{ sqlSession.close();
}
}
注:Executor在执行insertStudent(student)方法时,与事务的提交、回滚、关闭毫无瓜葛(方法内部不会提交、回滚事务),需 要像上面的代码一样,手动显示调用commit。、rollback。、close()等方法。
因此,后续在分析到类似insert。、update()等方法内部时,需要忘记事务的存在,不要试图在insert()等方法内部寻找有关事务的 任何方法。
4 .你可能关心的有关事务的几种特殊场景表现(重要)
- 一个conn生命周期内,可以存在无数多个事务。
〃执行yconnection.setAutoCommit(false),并返回
SqlSessionsqlSession=MybatisSqlSessionFactory.openSession();
try{
StudentMapperstudentMapper:sqlSession.getMapper(StudentMapper.class);
Studentstudent= newStudent();
student.setName( “yy”);
student.setEmail( “email@email.com”);
student.setDob( new Date());
student.setPhone( newPhoneNumber( “123-2568-8947”));
studentMapper.insertStudent(student);
//提交
sqlSession.commit();
studentMapper.insertStudent(student);
〃多次提交
sqlSession.commit();
} catch(Exceptione){
〃回滚,只能回滚当前未提交的事务
sqlSession.rollback();
} finally{
sqlSession.close();
}
对于JDBC来说,autoCommit=false时,是自动开启事务的,执行commit()后,该事务结束。
以上代码正常情况下,开启了2个事务,向数据库插入了2条数据。
JDBC中不存在Hibernate中的session的概念,在JDBC中,insert了几次,数据库就会有几条记录,切勿混淆。而rollback。,只 能回滚当前未提交的事务。 - autoCommit=false,没有执行commit(),仅执行close(),会发生什么?
try{
studentMapper.insertStudent(student);
}finally{
sqlSession.close();
}
就像上面这样的代码,没有commit(),固执的程序员总是好奇这样的特例。
insert后,close之前,如果数据库的事务隔离级别是read uncommitted,那么,我们可以在数据库中查询到该条记录。
接着执行sqlSession.close()时,经过SqlSession的判断,决定执行rollback。操作,于是,事务回滚,数据库记录消失。
下面,我们看看org.apache.ibatis.session.defaults.DefaultSqlSession.java中的close()方法源码。
@Override
public void close() {
try{
executor.close(isCommitOrRollbackRequired(false));
dirty= false;
} finally{
ErrorContext.instance().reset();
}
}
事务是否回滚,依靠isCommitOrRoUbackRequired(false)方法来判断。
private boolean isCommitOrRollbackRequired(booleanforce) { return(!autoCommit&&dirty)| force;
}
在上面的条件判断中,!autoCommit=true (取反当然是truey), force=false,最终是否回滚事务,只有dirty参数了,dirty含 义为是否是脏数据。
@Override
public int insert(Stringstatement,Objectparameter) {
returnupdate(statement,parameter);
}
@Override
public int update(Stringstatement,Objectparameter) {
try{
dirty: true;
MappedStatementms二configu ration.getMappedStatement(statement);
returnexecutor.update(ms,wrapCollection(parameter));
} catch(Exceptione){
throwExceptionFactory.wrapException(“Errorupdatingdatabase. Cause:”+e,e);
} finally{
ErrorContext.instance().reset();
}
}
源码很明确,只要执行update操作,就设置dirty=true。insert、delete最终也是执行update操作。
只有在执行完commit()、roUback()、close()等方法后,才会再次设置dirty=false。
@Override
public void commit(booleanforce) {
try{
executor.commit(isCommitOrRollbackRequired(force));
dirty= false;
} catch(Exceptione){
throwExceptionFactory.wrapException( “Errorcommittingtransaction. Cause:”+e,e);
} finally{
ErrorContext.instance().reset();
}
}
因此,得出结论:autoCommit=false,但是没有手动commit, 在sqlSession.close()时,Mybatis会将事务进行rollback。操作, 然后才执行conn.close()关闭连接,当然数据最终也就没能持久化到数据库中了。 - autoCommit=false,没有8由由储也没有日0$6,会发生什么?
studentMapper.insertStudent(student);
干脆,就这一句话,即不commit,也不close。
结论:insert后,jvm结束前,如果事务隔离级别是read uncommitted,我们可以查到该条记录。jvm结束后,事务被 rollback(),记录消失。通过断点debug方式,你可以看到效果。
这说明JDBC驱动实现,已经Kao虑到这样的特例情况,底层已经有相应的处理机制了。这也超出了我们的探究范围。
但是,一万个屈丝程序员会对你说:Don’t do it like this. Go right way。
警告:请按正确的try-catch-finally编程方式处理事务,若不从,本人概不负责后果。
注:无参的openSession()方法,会自动设置autoCommit=false。
总结:Mybatis的JdbcTransaction,和纯粹的Jdbc事务,几乎没有差别,它仅是扩展支持了连接池的connection。 另外,需要明确,无论你是否手动处理了事务,只要是对数据库进行任何update操作(update、delete、insert),都一定是在事 务中进行的,这是数据库的设计规范之一。
在使用mybatis过程中,当手写JavaBean和XML写的越来越多的时候,就越来越同意出错。这种重复性的工作,我们当然 不希望做那么多。
还好,mybatis为我们提供了强大的代码生成–MybatisGenerator。
通过简单的配置,我们就可以生成各种类型的实体类,Mapper接口,MapperXML文件,Example对象等。通过这些生成的文 件,我们就可以方便的进行单表进行增删改查的操作。
以下的工具使用的都是IDEA
1.1 创建Maven项目
1.1.1 菜单上选择新建项目
File | New | Project
1.1.2 选择左侧的Maven
,«Java Project SDK; | ■1S (java version ▼ New…
验 Java Enterprise
C J Boss 口 Create from archetype | Add Archetype-
■日 J2ME A com.at!assian.mave’ archetypes bamboo-plugin-archetype
■ Clouds ► com.Btlassian.maven.archetypes confluence-plugin-archetype
4 Spring > corruatlassian.maven^rchetypesyira-plugin-archetype
一 , 〜 ► com.rfc^noven.ordietj pe5jpa-mAven-archetype
Java FX
. ► de^kquinetjbossccybosscc-seam-archetype
Android > netdatabinder:data-app
. IntdliJ Platform Plugin > nediftweo lift-archetype-basic
噂 Spring Initializr ► netJiftweb lift-archetype-blank
/T) fl4jp/on > net.sf.mavcn harmaven-drehetype-har
► net.sf.maven–…ir;maven-archetype-iar
M Gradle 』 , ,. .
► c.rg.apache i ir-,r- r< h-‘’ - - xamel-archetype-activemq
Groovy > org.apache.camelarch€^pes:cannel-archetype*compon«nt
・ Griffon Ik camel-archetype-java
.Grails » orgrapache.camcl.arcbct)pe5:camel-archetype-scah
_ ► org-apa<he.c-amef.jrchet,»pe’s;canr)el-archetype-spring
♦ Application Forge
► org-apache.camel^rchetypet-xamel-archetype-war
・ S1a加 Web ► org.apache.cocoon;cocoon-22-archetype-block
弊 Hash ► org.apache.cocoon:cocoon-22-archetype-block-plain
K Kotlin » org.apache.cococnrcocoon-22-archetype-webapp
► org.apache.mavenujrchetyp,- maven-archetype-j2ee-simple
■ P" , ► org-«pacbe.meven.archetyp^:maven-archetype-marmalade-rnqjo
► org.apachr.maven.archetypt–. maven-archetype-mojo
► org.apache.maven.archetypesimaven-archetype-portiet
► org.apache.maven.archetjpc:;maven-archetype-profiles
:-h-丁册[壮?愉 esdn. net/weixin 3713919/
► crg epncHc.nuve^ arcnrtypr-; maven-drcnetype-srte
F
Bl New Project
X
由于我们只是创建一个普通的项目,此处点击Next即可。
1.1.3 输入GroupId和ArtifactId
•在我的项目中,
GroupId 填 com.homejim.mybatis
ArtifactId 填 mybatis-generator
点击Next。
1.1.4 Finish
通过以上步骤,一个普通的Maven项目就创建好了。
1.2 配置 generator.xml
其实名字无所谓,只要跟下面的pom.xml文件中的对应上就好了。
-K Compare With… Ctrl+D Database Changes tGeneia-
Compare File with Editor Designer
? Compare with Clipboard Event Log 的配置-
Quick Switch Scheme… Ctrl Image Layers
Toolbar
'成— 4 山蚌甲
View Navigate Code Analyze Refactor Build Run Tools VCS Window Hei J
1.4.2 Maven Projects 中双击 mybatis-generator
在右侧此时可以看到Maven Projects 了。找到mybatis-generator插件。
mybatis-generator | Plugins | mybatis-generator | mybatis-generator
▼篇 mybatis-generator
►彩 Lifecycle
▼畤 Plugins
焦 clean (org.apache.rr) ► (^compiler (org.apach ► deploy (org.apache. ► ^install (org.apache.n ►儒jar (org.apache.mav
▼像 mybatis-generator i mybatis-generat(
牛 mybatis-generat( ► 儒 resources (org.apac ►儒 site (org.apache.ma, ► surefire (org.apache
1.4.3双击运行
运行正确后,生成代码,得到如下的结构
V mybatis-generator C:\Users\Administrator\Doi
A ■ ,idea
▼ ■ src
▼ ■ main
▼ java
▼ A com.homejim.mybatis
▼ Ei entity
@ Blog
@ BlogExample
▼ B mapper
❶ BlogMapper
▼跑 resources
▼ EB mybatis.mapper
翡 Blog Mappenxml
品 generator.xml
A ■ test
mybatis-generator.iml
m pom.xml
仅仅是上面那么简单的使用还不够爽。那么我们就可以通过更改generator.xml配置文件的方式进行生成的配置。
2.1 文档
推荐查看官方的文档。
英文不错的:http://www.mybatis.org/generator/configreference/xmlconfig.html
中文翻译版:http://mbg.cndocs.ml/index.html
2.2 官网没有的
2.2.1 property 标签
该标签在官网中只是说用来指定元素的属性,至于怎么用没有详细的讲解。
2.2.1.1 分隔符相关
〈property name=“autoDelimitKeywords” value=“true”/>
〈property name=“beginningDelimiter” value=“'”/>
〈property name=“endingDelimiter” value="’ "/>
以上的配置对应的是mysql,当数据库中的字段和数据库的关键字一样时,就会使用分隔符。
比如我们的数据列是delete,按以上的配置后,在它出现的地方,就变成’delete’。
2.2.1.2 编码
默认是使用当前的系统环境的编码,可以配置为GBK或UTF-8。
〈property name=“javaFileEncoding” value=“UTF-8”/>
我想项目为UTF-8,如果指定生成GBK,则自动生成的中文就是乱码。
2.2.1.3 格式化
〈property name="javaFormatter“ value=“org.mybatis.generator.api.dom.DefaultJavaFormatter”/>
<property name="xmlFormatter“ value=“org.mybatis.generator.api.dom.DefaultXmlFormatter”/>
这些显然都是可以自定义实现的的。
2.2.2 plugins 标签
plugins标签用来扩展或修改代码生成器生成的代码。
在生成的XML中,是没有这个标签的。该标签是配置缓存的。
如果我们想生成这个标签,那么可以plugins中进行配置。
〈p山gin type=“org.mybatis.generator.p山gins.CacheP山gin” >
〈/p仙gin>
|caqh.e eviction= LRU=〉
WARNING - <Pmbg. generated
This element is automatically generated by MyBatis Generator, do not modify,
https://blog. csdn. net/weixin_37139197
比如你想生成的JavaBean中自行实现Serializable接口。
tudent inclements Sexializable (
- This field vas generated by MyBatis Generator.
- This field coiijesnonds to th^ database, colusu ftud典工 $宜去g
- https://biog. cson. net/weixin_3713219/
还能自定义插件。
这些插件都蛮有用的,感觉后续可以专门开一篇文章来讲解。
看名称,就知道是用来生成注释用的。
默认配置:
〈property name=“suppressA11comments” value=“false”/>
〈property name=“suppressDate” value=“false”/>
〈property name=“addRemarkComments” value=“false”/>
suppressAllComments:阻止生成注释,默认值是false。
suppressDate:阻止生成的注释包含时间戳,默认为false。
addRemarkComments:注释中添加数据库的注释,默认为false。
还有一个就是我们可以通过 type 属性指定我们自定义的注解实现类, 生成我们自己想要的注解。自定义的实现类需要实
现 org.mybatis.generator.api.CommentGenerator。
2.2.4源码
https://github.com/homejim/mybatis-cn
微信扫描二维码,关注我的公众号
阅读原文
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
MyBatis多数据源读写分离(注解实现)
Java后端 2019-11-09
点击上方Java后端,选择
优质文章,及时送达
作者 | hy_xiaobin
来源 | juejin.im/post/5d8705e65188253f4b629f47
首先需要建立两个库进行测试,我这里使用的是master_test和slave_test两个库,两张库都有一张同样的表(偷懒,喜喜),表结 构
表名t_user
字段名 类型 备注
id int 主键自增ID
name varchar 名称
表中分别添加两条不同数据,方便测试主数据库记录name为xiaobin,从库为xiaoliu开始使用Springboot整合mybatis,首先 引入pom文件
<?xml version="1.0" encoding="UTF-8"?>〈project xmlns=“http://maven.apache.org/POM/4.0.0”
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd”>
4.0.0
org.springframework.boot
spring-boot-sta rter-parent
2.1.4.RELEASE
com.xiaobin
mysql_master_slave
1.0-SNAPSHOT
<java.version>1.8</java.version>
<lombok.version>1.18.6</lombok.version>
<mybatis.version>1.3.2</mybatis.version>
<lombox.version>1.18.6</lombox.version>
〈dependencies〉
〈dependency〉
org.springframework.boot
spring-boot-starter-web
〈/dependency〉
<!一添加[ombok工具坐标–>
nrcr nrniprtlnmhnk
lombok</a rtifactId>
${lombok.version}
目录结构
源码地址(数据库需要自己创建)
https://gitee.com/MyXiaoXiaoBin/learning-to-share/tree/master/mysql_master_slave
-END-
学Java,请关注公众号:Java后端
喜欢文章,点个在看
阅读原文
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
MyBatis大揭秘:Plugin插件设计原理
Java后端 2019-12-02
点击上方Java后端,选择
优质文章,及时送达
作者|祖大俊
链接 | my.oschina.net/zudajun/blog/738973
大多数框架,都支持插件,用户可通过编写插件来自行扩展功能,Mybatis也不例外。
我们从插件配置、插件编写、插件运行原理、插件注册与执行拦截的时机、初始化插件、分页插件的原理等六个方面展开阐述。
1.插件配置
Mybatis的插件配置在configuration内部,初始化时,会读取这些插件,保存于Configuration对象的InterceptorChain中。
〈configuration)
〈property name=“value” value=“100” />
〈/configuration)
public class Configuration {
protected final InterceptorChain interceptorChain = new InterceptorChain(); }
org.apache.ibatis.plugin.InterceptorChain.java源码。
public class InterceptorChain {
private final List interceptors = new ArrayList();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor);
}
public List getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
上面的for循环代表了只要是插件,都会以责任链的方式逐一执行(别指望它能跳过某个节点),所谓插件,其实就类似于拦截
器。
2.如何编写一个插件
插件必须实现org.apache.ibatis.plugin.Interceptor接口。
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin (Object target);
void setProperties (Properties properties);
}
■ intercept。方法:执行拦截内容的地方,比如想收点保护费。由plugin()方法触发,interceptor.plugin(target)足以证 明。
■ plugin()方法:决定是否触发intercept。方法。
■ setProperties()方法:给自定义的拦截器传递xml配置的属性参数。
下面自定义一个拦截器:
@Intercepts({
@Signature(type = Executor.class, method = “query”, args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }),
@Signature(type = Executor.class, method = “close”, args = { boolean.class }) })
public class MyBatisInterceptor implements Interceptor {
private Integer value;
@Override
public Object intercept(Invocation invocation) throws Throwable { return invocation.proceed();
}
@Override
public Object plugin(Object target) {
System.out.println(value);
/Plugin类是插件的核心类,用于给arget创建一个JDK的动态代理对象,触发Mtercep(方法
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
value = Integer.valueOf((String) properties.get(“value”)); }
}
面对上面的代码,我们需要解决两个疑问:
1 .为什么要写Annotation注解?注解都是什么含义?
答:Mybatis规定插件必须编写Annotation注解,是必须,而不是可选。
@Intercepts注解:装载一 j@Signature列表,一个@Signature其实就是一个需要拦截的方法封装。那么,一个拦截器要拦截
多个方法,自然就是一个@Signature列表。
type = Executor.class, method = “'query”, args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
解释:要拦截Executor接口内的query()方法,参数类型为args列表。
2 . Plugin.wrap(target, this)是干什么的?
答:使用JDK的动态代理,给target对象创建一个delegate代理对象,以此来实现方法拦截和增强功能,它会回调intercept。方 法。
org.apache.ibatis.plugin.Plugin.java源码:
public class Plugin implements InvocationHandler {
private Object target;
private Interceptor interceptor;
private Map<Class<?>, Set> signatureMap; private Plugin(Object target, Interceptor interceptor, Map
推荐阅读
2 .你在公司项目里面看过哪些操蛋的代码?
3 .你知道 Spring Batch 吗?
4 .面试题:Lucene、Solr、ElasticSearch
5 . 3分钟带你彻底搞懂Java泛型背后的秘密
6 .团队开发中Git最佳实践
喜欢文章,点个在看
MyBatis源码:原来resultMap解析完是这样
阿进的写字台Java后端2019-11-16
更多详情,请参考mybatis百科-列映射类ResultMapping
1.2结果集映射类ResultMap
ResultMap对应的是结果集<resultMap>中的一^个结果集。其基本组成部分中,含有ResultMapping对象。
其组成大致如下:
更多详情,请参考mybatis百科-结果集映射类ResultMap
2.解析
2.1 人口函数
resultMap是mapper.xml文件下的,因此其是解析Mapper的一个环节。
resultMapElements(context.evalNodes(“/mapper/resultMap”));
解析<障531乂3口>,由于<resultMap>是可以有多个的, 因此,context.evalNodes(“/mapper/resultMap”)返回的是一^个
List
private void resultMapElements(List<XNode> list) throws Exception {
“遍历,解析
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
2.2 解析流程
整个过程就是resultMapElement这个函数。其流程大体如下
对应的代码
private ResultMap resultMapElement(XNode resultMapNode) throws Exception { return resultMapElement(resultMapNode, Collections. emptyList());
}
/*
"处理节点,将节点解析成ResultMap对象,下面包含有ResutMapping对象组成的列表
- @param resultMapNode resutMap 节点
" @param additionalResultMappings 另外的 ResutMapping 列
" @return ResultMap 对象
" @ th rows Exception
/
private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings) throws Exceptic ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
/获取ID,默认值会拼装所有父节点的id或value或property
String id = resultMapNode.getStringAttribute(“id”, resultMapNode.getVa山eBasedIdentifier());
/获取type属性,表示结果集将被映射为type指定类型的对象
String type = resultMapNode.getStringAttribute(“type”,
resultMapNode.getStringAttribute(“ofType”,
resultMapNode.getStringAttribute(“resultType”, resultMapNode.getStringAttribute(“javaType”))));
//获取extends属性,其表示结果集的继承
String extend = resultMapNode.getStringAttribute(“extends”);
/自动映射属性。将列名自动映射为属性
Boolean autoMapping = resultMapNode.getBooleanAttribute(“autoMapping”);
//解析type,获取其类型
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
/记录解析的结果
List resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
//处理子节点
List resultchildren = resultMapNode.getChildren();
for (XNode resultchild : resultchildren) {
// 处理 constructor 节点
if (“constructor”.equals(resultChild.getName())) {
//解析构造函数元素,其下的没每一个子节点都会生产一个ResutMapping对象 processConstructorElement(resultChild, typeClass, resultMappings);
// 处理 discriminator 节点
} else if (“discriminator”.equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
//处理其余节点,如 id, result, assosation d等
} else {
List flags = new ArrayList<>();
if (“id”.equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 创建 resutMapping 对象,并添加到 resultMappings 中
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//创建ResultMapResolver对象,该对象可以生成ResultMap对象
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultM try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
〃如果无法创建ResultMap对象,则将该结果添加到incompleteResultMaps集合中
configuration.addlncompleteResultMap(resultMapResolver);
throw e;
id对于resultMap来说是很重要的,它是一个身份标识。具有唯一性
“获取ID,默认值会拼装所有父节点的id或value或property。
String id = resultMapNode.getStringAttribute(“id”, resultMapNode.getValueBasedIdentifier());
这里涉及到XNode对象中的两个函数
public String getStringAttribute (String name, String def) {
String value = attributes.getProperty(name);
if (value == null) {
return def;
} else {
return value;
}
}
该函数是获取XNode对象对应XML节点的name属性值,如果该属性不存在,则返回传入的默认值def
而在获取id的过程中,默认值是下面这个函数
/*
*生成元素节点的基础id - @ return
/
public String getValueBasedIdentifier() {
StringBuilder builder = new StringBuilder();
XNode current = this;
//当前的节点不为空
while (current != null) {
“如果节点不等于this,则在0之前插入_符号,因为是不断的获取父节点的,因此是插在前面
if (current != this) {
builder.insert(0, “");
}
//获取id, id不存在则获取value, value不存在则获取property。
String value = current.getStringAttribute(“id”,
current.getStringAttribute(“value”,
current.getStringAttribute(“property”, null)));
/ value非空,则将.替换为一 并将value的值加上]
if (value != null) {
value = value.replace(‘.’, '');
builder.insert(0, “]”);
builder.insert(0,
value);
builder.insert(0, “[”);
}
/不管value是否存在,前面都添加上节点的名称
builder.insert(0, current.getName());
〃获取父节点
current = current.getParent();
}
return builder.toString();
}
该函数是生成元素节点的id,如果是这样子的XML
〈employee id=”${id_var}“>
<first_n ame>Jim </first_ name >
<last_name>Smith</last_name>
<birth_date>
1970
6
15
</birth_date>
〈height units=“ft”>5.8
〈weight units=“lbs”>200
true
〈/employee〉
我们调用
XNode node = parser.evalNode(”/employee/height"); node.getVa山eBasedIdentifier();
则,返回值应该是
employee] {id_var}]_height
2.4 解析结果集的类型
结果集的类型,对应的是一个JavaBean对象。通过反射来获得该类型。
/获取type, type不存在则获取ofType, ofType
//不存在则获取resultType, resultType不存在则获取javaType
String type = resultMapNode.getStringAttribute(“type”,
resultMapNode.getStringAttribute(“ofType”,
resultMapNode.getStringAttribute(“resultType”, resultMapNode.getStringAttribute(“javaType”))));
”……
/获取type对应的Class对象
Class<?> typeClass = resolveClass(type);
看源码,有很多个def值,也就是说,我们在配置结果集的类型的时候都是有优先级的。但是,这里有一个奇怪的地方,我源
代码版本(3.5.0-SNAPSHOT)的 的属性,只有 type,没有 ofType/resultType/javaType。以下为相应的 DTD 约 束:
我怀疑是兼容以前的版本。
2.5 获取继承结果集和自动映射
String extend = resultMapNode.getStringAttribute(“extends”);
Boolean autoMapping = resultMapNode.getBooleanAttribute(“autoMapping”);
这个两个属性都是在配置XML的时候可有可无的。
先看DTD约束
/将从其他地方传入的additonaResutMappings添加到该链表中
resultMappings.addAll(additionalResultMappings);
//获取子节点
List resultchildren = resultMapNode.getChildren();
//遍历解析子节点
for (XNode resultchild : resultchildren) {
if (“constructor”.equals(resultChild.getName())) {
〃解析构造函数元素,其下的没每一个子节点都会生产一个ResutMapping对象
processConstructorElement(resultChild, typeClass, resultMappings);
} else if (“discriminator”.equals(resultChild.getName())) {
// 解析 discriminator 节点
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
//解析其余的节点
List flags = new ArrayList<>();
if (“id”.equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
除了 discriminator节点,其余节点最后都会回到buildResultMappingFromContext方法上,该方法是创建 ResultMapping
对象。
*
*获取一行,如]03等,取得他们所有的属性,通过这些属性建立ResultMapping对象
- @param context对于节点本身
- @param resultType resutMap 的结果类型
- @param flags fag属性,对应ResutFiag枚举中的属性。一般情况下为空
- @ return 返回 ResultMapping
- @ th rows Exception
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List flags) throws Ex String property; //获取节点的属性,如果节点是构造函数(只有。,由自属性,没有popery), //则获取的是name,否则获取property if (flags.contains(ResultFlag.CONSTRUCTOR)) { property = context.getStringAttribute("name"); } else { property = context.getStringAttribute("property"); } String column = context.getStringAttribute("column"); String javaType = context.getStringAttribute("javaType"); String jdbcType = context.getStringAttribute("jdbcType"); String nestedSelect = context.getStringAttribute("select"); //获取嵌套的结果集 String nestedResultMap = context.getStringAttribute("resultMap", processNestedResultMappings(context, Collections. emptyList())); String notNullColumn = context.getStringAttribute("notNullColumn"); String columnPrefix = context.getStringAttribute("columnPrefix"); String typeHandler = context.getStringAttribute("typeHandler"); String resultSet = context.getStringAttribute("resultSet"); String foreignColumn = context.getStringAttribute("foreignColumn"); boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager") //以上获取各个属性节点 〃解析 javaType, typeHandler, jdbcType Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings(“unchecked”)
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 仓9建resutMapping对"象
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, neste
}
如果是discriminator,则处理该元素并创建鉴别器。
/*
- 处理鉴别器
- @param context 节点
- @param resultType 结果类型
- @param resultMappings 列结果集合
- @return鉴别器
- @ th rows Exception
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List resultMappings) String column = context.getStringAttribute("column"); String javaType = context.getStringAttribute("javaType"); String jdbcType = context.getStringAttribute("jdbcType"); String typeHandler = context.getStringAttribute("typeHandler"); 〃先获取各个属性 /取得jav Type对应的类型 Class<?> javaTypeClass = resolveClass(javaType);
取得typeHandler对应的类型
@SuppressWarnings(“unchecked”)
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
“取得jdbcType对应的类型
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 创建 discriminatorMap,并遍历子节点,以 vaiue->resutMap 的方式放入 discriminatorMap中
Map<String, String> discriminatorMap = new HashMap<>();
for (XNode caseChild : context.getChildren()) {
String value = caseChild.getStringAttribute(“value”);
String resultMap = caseChild.getStringAttribute(“resultMap”, processNestedResultMappings(caseChild, resultMappings));
discriminatorMap.put(value, resultMap);
}
/创建鉴别器
return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminat }
鉴别器内部,也是含有ResultMapping的
public class Discriminator {
private ResultMapping resultMapping;
private Map<String, String> discriminatorMap;
}
2.7创建ResultMap对象 在解析完<resultMap>的各个属性和子节点之后。创建ResultMapResolver对象,通过对象可以生成ResultMap。
/
"创建并添加ResutMap到Configuration对象中
@param id id,配置了 id可以提高效率
" @param type 类型
@param extend 继承
@param discriminator 鉴别器
@param resutMappings 列集
@param autoMapping是否自动映射
@ ret urn返回创建的ResutMap对象
*
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List resultMappings,
Boolean autoMapping) {
id = applyCurrentNamespace(id, false);
extend = applyCurrentNamespace(extend, true);
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException(“Could not find a parent resultmap with id '” + extend + “'”); }
//从 configuration中获取继承的结果集
ResultMap resultMap = configuration.getResultMap(extend);
//获取所集成结果集的所有ResutMapping集合
List extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
/移除需要覆盖的ResutMapping集合
extendedResultMappings.removeAll(resultMappings);
〃如果该resultMap中定义了构造节点,则移除其父节点的构造器
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
Iterator extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) { extendedResultMappingsIter.remove();
}
}
}
“添加需要被继承的ResutMapping集合
resultMappings.addAll(extendedResultMappings);
}
/通过建造者模式创建ResultMap对象
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) .discriminator(discriminator) .build();
*添加到Configuration对象中
configuration.addResultMap(resultMap);
return resultMap;
}
3解析结果
有如下的数据库表
通过代码生成器生成XML和Mapper。
添加结果集
<resultMap id=wdetailedBlogResultMapM type»nBlog">
<!— 定义映射中使用的构造函数 —>
推荐阅读
1从零搭建一个Spring Boot开发环境
2 .Redis实现「附近的人」这个功能
3 . 一个秒杀系统的设计思考
4,零基础认识Spring Boot
5.团队开发中Git最佳实践
喜欢文章,点个在看
阅读原文
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
Spring Boot + MyBatis + Druid + PageHelper 实现多数据源并分页
虚无境Java后端2019-11-12
接收文章,更加及时
作者|虚无境
链接 | cnblogs.com/xuwujing/p/8964927.html
刖后
本篇文章主要讲述的是Spring Boo t整合Mybatis、Druid和PageHelper并实现多数据源和分页。其中Spring Boot整 合Mybatis这块,在之前的的一篇文章中已经讲述了,这里就不过多说明了。重点是讲述在多数据源下的如何配置使用 Druid和PageHelper。
http://www.cnblogs.eom/xuwujing/p/8260935.html
Druid介绍和使用
在使用Druid之前,先来简单的了解下Druid。
Druid是一个数据库连接池。Druid可以说是目前最好的数据库连接池!因其优秀的功能、性能和扩展性方面,深受开发人员 的青睐。
Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。Druid是阿里巴巴开发的号称 为监控而生的数据库连接池!
同时Druid不仅仅是一个数据库连接池,Druid核心主要包括三部分:
■ 基于Filter-Chain模式的插件体系。
■ DruidDataSource高效可管理的数据库连接池。
■ SQLParser
Druid的主要功能如下:
■ 是一个高效、功能强大、可扩展性好的数据库连接池。
■ 可以监控数据库访问性能。
■ 数据库密码加密
■ 获得SQL执行日志
■ 扩展JDBC
介绍方面这块就不再多说,具体的可以看官方文档。那么开始介绍Druid如何使用。
首先是Maven依赖,只需要添加druid这一个jar就行了。
〈dependency〉
com.alibaba
druid
1.1.8
〈/dependency〉
Tips:可以关注微信公众号:Java后端,获取Maven教程和每日技术博文推送。
配置方面,主要的只需要在application.properties或application.yml添加如下就可以了。
说明:因为这里我是用来两个数据源,所以稍微有些不同而已。Druid配置的说明在下面中已经说的很详细了,这里我 就不在说明了。
##默认的数据源
master.datasource.url=jdbc:mysql://localhost:3306/springBoot?useUnicode=true&characterEncoding=utf8&allowMultiQueries二 master.datasource.username=root
master.datasource.password=123456
master.datasource.driverClassName=com.mysql.jdbc.Driver
##另一个的数据源
duster.datasource.url=jdbc:mysql://localhost:3306/springBoot_test?useUnicode=true&characterEncoding=utf8
duster.datasource.username=root
cluster.datasource.password=123456
cluster.datasource.driverClassName=com.mysql.jdbc.Driver
#连接池的配置信息
#初始化大小,最小,最大
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
#配置获取连接等待超时的时间
spring.datasource.maxWait=60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
#配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileldle=true
spring.datasou rce.testO n Borrow=fa Ise
spring.datasou rce.testO n Return=fa Ise
#打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
#配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall’用于防火墙
spring.datasou rce.filte rs=stat,wa ll,log4j
#通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
I Jd
成功添加了配置文件之后,我们再来编写Druid相关的类。
首先是MasterDataSourceConfig.java这个类,这个是默认的数据源配置类。
@Configuration
@MapperScan(basePackages = MasterDataSourceConfig.PACKAGE, sqlSessionFactoryRef = “masterSqlSessionFactory”)
public class MasterDataSourceConfig {
static final String PACKAGE = “com.pancm.dao.master”;
static final String MAPPER_LOCATION = “classpath:mapper/master/*.xml”;
@Value(“
m
a
s
t
e
r
.
d
a
t
a
s
o
u
r
c
e
.
u
r
l
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
u
r
l
;
@
V
a
l
u
e
(
"
{master.datasource.url}") private String url; @Value("
master.datasource.url")privateStringurl;@Value("{master.datasource.username}”) private String username;
@Value(“
m
a
s
t
e
r
.
d
a
t
a
s
o
u
r
c
e
.
p
a
s
s
w
o
r
d
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
p
a
s
s
w
o
r
d
;
@
V
a
l
u
e
(
"
{master.datasource.password}") private String password; @Value("
master.datasource.password")privateStringpassword;@Value("{master.datasource.driverClassName}”) private String driverClassName;
@Value(“
s
p
r
i
n
g
.
d
a
t
a
s
o
u
r
c
e
.
i
n
i
t
i
a
l
S
i
z
e
"
)
p
r
i
v
a
t
e
i
n
t
i
n
i
t
i
a
l
S
i
z
e
;
@
V
a
l
u
e
(
"
{spring.datasource.initialSize}") private int initialSize; @Value("
spring.datasource.initialSize")privateintinitialSize;@Value("{spring.datasource.minIdle}”)
private int minIdle;
@Value(“
s
p
r
i
n
g
.
d
a
t
a
s
o
u
r
c
e
.
m
a
x
A
c
t
i
v
e
"
)
p
r
i
v
a
t
e
i
n
t
m
a
x
A
c
t
i
v
e
;
@
V
a
l
u
e
(
"
{spring.datasource.maxActive}") private int maxActive; @Value("
spring.datasource.maxActive")privateintmaxActive;@Value("{spring.datasource.maxWait}”)
private int maxWait;
@Value(“
s
p
r
i
n
g
.
d
a
t
a
s
o
u
r
c
e
.
t
i
m
e
B
e
t
w
e
e
n
E
v
i
c
t
i
o
n
R
u
n
s
M
i
l
l
i
s
"
)
p
r
i
v
a
t
e
i
n
t
t
i
m
e
B
e
t
w
e
e
n
E
v
i
c
t
i
o
n
R
u
n
s
M
i
l
l
i
s
;
@
V
a
l
u
e
(
"
{spring.datasource.timeBetweenEvictionRunsMillis}") private int timeBetweenEvictionRunsMillis; @Value("
spring.datasource.timeBetweenEvictionRunsMillis")privateinttimeBetweenEvictionRunsMillis;@Value("{spring.datasource.minEvictableIdleTimeMillis}”)
private int minEvictableIdleTimeMillis;
@Value(“
s
p
r
i
n
g
.
d
a
t
a
s
o
u
r
c
e
.
v
a
l
i
d
a
t
i
o
n
Q
u
e
r
y
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
v
a
l
i
d
a
t
i
o
n
Q
u
e
r
y
;
@
V
a
l
u
e
(
"
{spring.datasource.validationQuery}") private String validationQuery; @Value("
spring.datasource.validationQuery")privateStringvalidationQuery;@Value("{spring.datasource.testWhileIdle}”)
private boolean testWhileIdle;
@Value(“
s
p
r
i
n
g
.
d
a
t
a
s
o
u
r
c
e
.
t
e
s
t
O
n
B
o
r
r
o
w
"
)
p
r
i
v
a
t
e
b
o
o
l
e
a
n
t
e
s
t
O
n
B
o
r
r
o
w
;
@
V
a
l
u
e
(
"
{spring.datasource.testOnBorrow}") private boolean testOnBorrow; @Value("
spring.datasource.testOnBorrow")privatebooleantestOnBorrow;@Value("{spring.datasource.testOnReturn}”)
private boolean testOnReturn;
@Value(“
s
p
r
i
n
g
.
d
a
t
a
s
o
u
r
c
e
.
p
o
o
l
P
r
e
p
a
r
e
d
S
t
a
t
e
m
e
n
t
s
"
)
p
r
i
v
a
t
e
b
o
o
l
e
a
n
p
o
o
l
P
r
e
p
a
r
e
d
S
t
a
t
e
m
e
n
t
s
;
@
V
a
l
u
e
(
"
{spring.datasource.poolPreparedStatements}") private boolean poolPreparedStatements; @Value("
spring.datasource.poolPreparedStatements")privatebooleanpoolPreparedStatements;@Value("{spring.datasource.maxPoolPreparedStatementPerConnectionSize}”) private int maxPoolPreparedStatementPerConnectionSize;
@Value(“KaTeX parse error: Expected 'EOF', got '}' at position 1862: …getObject(); } }̲ 其中这两个注解说明下: ■ …{cluster.datasource.url}”) private String url;
@Value(“
c
l
u
s
t
e
r
.
d
a
t
a
s
o
u
r
c
e
.
u
s
e
r
n
a
m
e
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
u
s
e
r
n
a
m
e
;
@
V
a
l
u
e
(
"
{cluster.datasource.username}") private String username; @Value("
cluster.datasource.username")privateStringusername;@Value("{cluster.datasource.password}”) private String password;
@Value(“${cluster.datasource.driverClassName}”) private String driverClass;
//fUMasterDataSourceConfig—^,这里略
@Bean(name = “clusterDataSource”)
public DataSource clusterDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driverClass);
//和MasterDataSourceConfg一样,这里略…
return dataSource;
}
@Bean(name = “clusterT ransactionManager”)
public DataSourceTransactionManager clusterTransactionManager() {
return new DataSourceTransactionManager(clusterDataSource()); }
@Bean(name = “clusterSqlSessionFactory”)
public SqlSessionFactory clusterSqlSessionFactory(@Qualifier(“clusterDataSource”) DataSource clusterDataSource)
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(clusterDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(ClusterDataSourceConfig.M> return sessionFactory.getObject();
}
}
1 2d
成功写完配置之后,启动程序,进行测试。
分别在springBoot和springBoot_test库中使用接口进行添加数据。
t user —
POST http://localhost:8084/api/user
{“name”:“张三”,“age”:25}
{“name”:“李四”,“age”:25}
{“name”:“王五”,“age”:25}
t student —
POST http://localhost:8084/api/student
{“name”:“学生A”,“age”:16}
{“name”:“学生B”,“age”:17}
{“name”:“学生C”,“age”:18}
成功添加数据之后,然后进行调用不同的接口进行查询。
请求:
GET http://localhost:8084/api/user?name=李四
返回:
{
“id”: 2,
“name”:“李四”,
“age”: 25
}
请求:
GET http://localhost:8084/api/student?name=学生C
返回:
{
“id”: 1,
“name”:“学生C”,
“age”: 16
}
通过数据可以看出,成功配置了多数据源了。
PageHelper分页实现
PageHelper是Mybatis的一个分页插件,非常的好用!这里强烈推荐!!!
PageHelper的使用很简单,只需要在Maven中添加pagehelper这个依赖就可以了。
Maven的依赖如下:
〈dependency〉
com.github.pagehelper
pagehelper-spring-boot-starter
1.2.3
〈/dependency〉
注:这里我是用springBoot版的!也可以使用其它版本的。
添加依赖之后,只需要添加如下配置或代码就可以了。
第一种,在application.properties或application.yml添加
pagehelper:
helperDialect: mysql
offsetAsPageNum: true
rowBoundsWithCount: true reasonable: false
第二种,在mybatis.xml配置中添加
〈property name=“dataSource” ref=“dataSource” />
<–配置分页插件–>
helperDialect=mysql
offsetAsPageNum=true
rowBoundsWithCount=true
reasonable=false
第三种,在代码中添加,使用@Bean注解在启动程序的时候初始化。
@Bean
public PageHelper pageHelper(){
PageHelper pageHelper = new PageHelper();
Properties properties = new Properties。;
〃数据库
properties.setProperty(“helperDialect”, “mysql”);
〃是否将参数offset乍为PagNum使用
properties.setProperty(“offsetAsPageNum”, “true”);
//是否进行count查询
properties.setProperty(“rowBoundsWithCount”, “true”);
〃是否分页合理化
properties.setProperty(“reasonable”, “false”);
pageHelper.setProperties(properties); }
因为这里我们使用的是多数据源,所以这里的配置稍微有些不同。我们需要在sessionFactory这里配置。这里就对 MasterDataSourceConfig.java进行相应的修改。
在masterSqlSessionFactory方法中,添加如下代码。
@Bean(name = “masterSqlSessionFactory”)
@Primary
public SqlSessionFactory masterSqlSessionFactory(@Qualifier(“masterDataSource”) DataSource masterDataSource) throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(masterDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(MasterDataSourceConfig.MAPPER_LOCATION));
〃分页插件
Interceptor interceptor = new PageInterceptor();
Properties properties = new Properties。;
〃数据库
properties.setProperty(“helperDialect”, “mysql”);
〃是否将参数offset乍为PagNum使用
properties.setProperty(“offsetAsPageNum”, “true”);
//是否进行count查询
properties.setProperty(“rowBoundsWithCount”, “true”);
〃是否分页合理化
properties.setProperty(“reasonable”, “false”);
interceptor.setProperties(properties);
sessionFactory.setPlugins(new Interceptor[] {interceptor});
return sessionFactory.getObject();
}
注:其它的数据源也想进行分页的时候,参照上面的代码即可。
这里需要注意的是年35。”353参数,表示分页合理化,默认值为false。如果该参数设置为true时,pageNum<=0时会查
询第一页,pageNum>pages(超过总数时),会查询最后一页。默认false时,直接根据参数进行查询。
设置完PageHelper之后,使用的话,只需要在查询的sql前面添加PageHelper.startPage(pageNum,pageSize);,如果
是想知道总数的话,在查询的sql语句后买呢添加page.getTotal()就可以了。
代码示例:
public List findByListEntity(T entity) {
List list = null;
try {
Page<?> page =PageHelper.startPage(1,2);
System.out.println(getClassName(entity)+”设置第一页两条数据!”);
list = getMapper().findByListEntity(entity);
System.out.println(“总共有:”+page.getTotal()+”条数据,实际返回:"+list.size()+"两条数据!”);
} catch (Exception e) {
logger.error(“查询”+getClassName(entity)+“失败!原因是:”,e);
}
return list;
代码编写完毕之后,开始进行最后的测试。
查询t_user表的所有的数据,并进行分页。
请求:
GET http://localhost:8084/api/user
返回:
[
{
“id”: 1,
“name”:“张三”,
“age”: 25
},
{
“id”: 2,
“name”:“李四”,
“age”: 25
}
]
控制台打印:
开始查询…
User设置第一页两条数据!
2018-04-27 19:55:50.769 DEBUG 6152 — [io-8084-exec-10] c.p.d.m.UserDao.findByListEntity_COUNT : ==> Preparing: SELECT cou
2018-04-27 19:55:50.770 DEBUG 6152 — [io-8084-exec-10] c.p.d.m.UserDao.findByListEntity_COUNT : > Parameters:
2018-04-27 19:55:50.771 DEBUG 6152 — [io-8084-exec-10] c.p.d.m.UserDao.findByListEntity_COUNT : < Total: 1
2018-04-27 19:55:50.772 DEBUG 6152 — [io-8084-exec-10] c.p.dao.master.UserDao.findByListEntity : ==> Preparing: select id, nar
2018-04-27 19:55:50.773 DEBUG 6152 — [io-8084-exec-10] c.p.dao.master.UserDao.findByListEntity : > Parameters: 2(Integer)
2018-04-27 19:55:50.774 DEBUG 6152 — [io-8084-exec-10] c.p.dao.master.UserDao.findByListEntity : < Total: 2 总共有:3条数据,实际返回:2两条数据!
W 1 E
查询t_student表的所有的数据,并进行分页。
请求:
GET http://localhost:8084/api/student
返回:
[
{
“id”: 1,
“name”:“学生人”,
“age”: 16
},
{
“id”: 2,
“name”:“学生8”,
“age”: 17
}
]
控制台打印:
开始查询…
Studnet设置第一页两条数据!
2018-04-27 19:54:56.155 DEBUG 6152 — [nio-8084-exec-8] c.p.d.c.S.findByListEntity_COUNT : ==> Preparing: SELECT count(0) FR
2018-04-27 19:54:56.155 DEBUG 6152 — [nio-8084-exec-8] c.p.d.c.S.findByListEntity_COUNT : > Parameters:
2018-04-27 19:54:56.156 DEBUG 6152 — [nio-8084-exec-8] c.p.d.c.S.findByListEntity_COUNT : < Total: 1
2018-04-27 19:54:56.157 DEBUG 6152 — [nio-8084-exec-8] c.p.d.c.StudentDao.findByListEntity : ==> Preparing: select id, name, a:
2018-04-27 19:54:56.157 DEBUG 6152 — [nio-8084-exec-8] c.p.d.c.StudentDao.findByListEntity : > Parameters: 2(Integer)
2018-04-27 19:54:56.157 DEBUG 6152 — [nio-8084-exec-8] c.p.d.c.StudentDao.findByListEntity : < Total: 2 总共有:3条数据,实际返回:2两条数据!
8) E
查询完毕之后,我们再来看Druid的监控界面。
可以很清晰的看到操作记录!
如果想知道更多的Druid相关知识,可以查看官方文档!
结语
这篇终于写完了,在进行代码编写的时候,碰到过很多问题,然后慢慢的尝试和找资料解决了。本篇文章只是很浅的介绍了 这些相关的使用,在实际的应用可能会更复杂。如果有有更好的想法和建议,欢迎留言进行讨论!
参考文章:
https://www.bysocket.com/?p=1712
Durid官方地址:
https://github.com/alibaba/druid
PageHelper官方地址:
https://github.com/pagehelper/Mybatis-PageHelper
文中源码:
https://github.com/xuwujing/springBoot
-END-
喜欢文章,点个在看
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
Spring Boot + MyBatis多模块项目搭建教程
枫本非凡Java后端2019-09-30
点击上工Java后端选择“设为星标:
优质文章,及时送达
作者|枫本非凡
链接 | cnblogs.com/orzlin/p/9717399.html
上篇| IDEA远程一键部署Spring Boot至U Docker
一、前言
最近公司项目准备开始重构,框架选定为SpringBoot+Mybatis,本篇主要记录了在2£人中搭建SpringBoot多模块项目的过程。
1、开发工具及系统环境
• IDE:
IntelliJ IDEA 2018.2
• 系统环境:
mac OSX
2、项目目录结构
• biz层:
业务逻辑层
• dao层:
数据持久层
• web层:
请求处理层
二、搭建步骤
1、创建父工程
IDEA 工具栏选择菜单 File -> New -> Project…
选择Spring Initializr,Initializr默认选择Default,点击Next
填写输入框,点击Next
这步不需要选择直接点Next
点击尸而5卜创建项目
最终得到的项目目录结构如下
beta [-/Workspace/JAVA/beta]
、Add Configuration.
mW to
国 Project •
▼ ybeta tspece/jAVA/beta
► iBidM
►・.mvn
►・src
日.gitIgnore
■ beta.iml
(| mvnw
(| mvnw.ond
息 pom. xml
► Ufa External Libraries
► ’ Scratches and Consoles
Search Everywhere Double 分
Go to File OXN
Recent Files *E
Navigation Bar ‘’
Drop files here to open
■ Terminal 3 g: TODO
QEwntLog
JReM renxxe servari log
1783 at 2014M
删除无用的.mvn目录、src目录、mvnw及mvnw.cmd文件,最终只留相照"0『6和pom.xml
■ beta [-/Wockspace/JAVA/beta] - beta
BeUApplication ▼
<project «lns=,http://Baven.apache.org/POM/4.9.9" XBlns:xsi=,http://ww.w3.org/2Ml/XMLSche«a-instance" ,八. xsi:scheaLocation-"http://aaven.apache.Org/P0M/4.0.0http://Baven.apache.Org/xsd/aaven-4.0.0.) [ D 9
■caode iVe rs ion>4.0.0</aode iVe rs ion>
coa.yibao.beta
beta</a rtifactld>
l.0.0-SNAPSHOT
Jar
beta
Oeao project for Spring Boot
org.springfraaework.boot
spring-boot-starter-parent
2.0.3. RELEASE, ve rs ion>
<!— lookup parent fro« repository —>
</parent
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding〉
<java.version>l.8</java.version>
〈/properties〉
dependencies*
org.springfraaework.boot
spring-boot-starter
<flroupldx) rg.springfraaework.boot
spring-boot-starter-test
test
〈/dependency*
〈/dependencies〉
project name
B Terminal . Spring 3 Q: TOGO Q Evant Log ,J Rebel remote sarvart ICQ
2、创建子模块
选择项目根目录beta右键呼出菜单,选择New -> Module
IntelliJ IDEA File Edit
View
Navigate Code Analyze Refactor
Build RL
New
Add Framework Support…
HprX Cut
,” । 晅 Copy
Copy Path
Copy Relative Path □ Paste
选择Maven, 点击Next
, Java
咻 Java Enterprise
C JBoss
Module SDK: 蟹 Project SDK (1.8)
Create from archetype
► 12 Module
i File
邕 Scratch File
邦X ■i Directory
3€C
。用C HTML File
翡 Stylesheet
邦V JavaScript File
• Y fl 21 FT
New Module
New.
3d Archetype…
(8 J2ME
・ Clouds
“ Spring
(D1 Android
f IntelliJ Platform Plugin
/ Spring Initializr
Maven
. Gradle
0 Groovy
■ Griffon
■ Grails
• Application Forge
• Static Web
■ Flash
K Kotlin
► com.atlassian.maven.archetypesbamboo-plugin-archetype
► com.atlassian.maven.archetypes confluence-plugin-archetype
► com.atlassian.maven.archetypesjira-plugin-archetype
► com.rfc.maven.archetypesjpa-maven-archetype
► de.akquinet.jbossccjbosscc-seam-archetype
► net.databinder :data-app
► net.liftweb:lift-archetype-basic
► net.liftweb:lift-archetype-blank
► n et. sf. maven - h a r : maven -archetype-har
► net.sf.maven-sar maven-archetype-sar
► org.apache.camel.archetypescamel-archetype-activemq
► org.apache.camel.archetypes camel-archetype-component
► org.apache.camel.archetypescamel-archetype-java
► org.apache.camel.archetypescamel-archetype-scala
► org.apache.camel.archetypes: camel-archetype-spring
► org.apache.camel.archetypes:camel-archetype-war
► org.apache.cocoon cocoon-22-archetype-block
► org.apache.cocoon :cocoon-22-archetype-block-plain
► org.apache.cocoon cocoon-22-archetype-webapp
► org.apache.maven.archetypes:maven-archetype-j2ee-simple
► org.apache.maven.archetypes:maven-archetype-marmalade-mojo
► org.apache.maven.archetypes:maven-archetype-mojo
► org.apache.maven.archetypes:maven-archetype-porttet
► org.apache.maven.archetypes:maven-archetype-profiles
► orn anarhR mavnn archatvn-mavRn-archatvna-ciiickstart
Help
Cancel
填写ArifactId,点击Next
修改Module name增加横杠提升可读性,点击尸血5h
同理添加beta-dao、beta-web子模块,最终得到项目目录结构如下图
■ beta ['/Workspace/JAVA/betaJ - beta
3、运行项目
在beta-web层创建com.yibao.beta.web包(注意:这是多层目录结构并非单个目录名,com >> yibao >> beta >> web)并添 力口入口类BetaWebApplication.java
@SpringBootApplication
public class BetaWebApplication {
3
public static void main(String[] args) {
SpringApplication.run(BetaWebApplication.class, args); } }
在com.yibao.beta.web包中添加controller目录并新建一个controller,添加test方法测试接口是否可以正常访问
@RestController
@RequestMapping(“demo”) public class DemoController {
5 @GetMapping(“test”
6 )
public String test() { return “Hello World!”
;
运行BetaWebApplication类中的main方法启动项目,默认端口为8080,访问http://localhost:8080/demo/test得到如下效果
.localhost:8080/demo/test X +
<–> C © localhost:8080/demo/test
Hello World!
以上虽然项目能正常启动,但是模块间的依赖关系却还未添加,下面继续完善。微信搜索web_esouce获取更多推送
4、配置模块间的依赖关系
各个子模块的依赖关系:biz层依赖dao层,web层依赖biz层
父pom文件中声明所有子模块依赖(dependencyManagement及dependencies的区别自行查阅文档)
(dependencies
3 >
(dependency
com.yibao.beta</groupId
beta-biz</artifactId
9 >
b
e
t
a
.
v
e
r
s
i
o
n
<
/
v
e
r
s
i
o
n
11
>
〈
/
d
e
p
e
n
d
e
n
c
y
13
>
<
d
e
p
e
n
d
e
n
c
y
15
>
<
g
r
o
u
p
I
d
>
c
o
m
.
y
i
b
a
o
.
b
e
t
a
<
/
g
r
o
u
p
I
d
17
>
<
a
r
t
i
f
a
c
t
I
d
>
b
e
t
a
−
d
a
o
<
/
a
r
t
i
f
a
c
t
I
d
19
>
<
v
e
r
s
i
o
n
>
{beta.version}</version 11 > 〈/dependency 13 > <dependency 15 > <groupId>com.yibao.beta</groupId 17 > <artifactId>beta-dao</artifactId 19 > <version>
beta.version</version11>〈/dependency13><dependency15><groupId>com.yibao.beta</groupId17><artifactId>beta−dao</artifactId19><version>{beta.version}</version
</dependency
<dependency
com.yibao.beta</groupId
beta-web</artifactId
${beta.version}</version
</dependency
「dependencies epen e nc e s
其中$也613入640口}定义在properties标签中
在beta-web层中的pom文件中添加beta-biz依赖
〈dependencies〉
(dependency
com.yibao.beta</groupId
beta-biz</artifactId
(/dependency
在beta-biz层中的pom文件中添加beta-dao依赖
<dependency
com.yibao.beta</groupId
beta-dao</artifactId
</dependency
- web层调用biz层接口测试
在beta-biz层创建com.yibao.beta.biz包,添加service目录并在其中创建DemoService接口类,微信搜索web_resource获取 更多推送
public interface DemoService {
2} String test0
@Service
public class DemoServicelmpl implements DemoService {
4 ©Override
public String test()
return “test”
}
DemoController通过@Autowired注解注入DemoService,修改DemoController的test方法使之调用DemoService的test方 法,最终如下所示:
package com.yibao.beta.web.controller; @RestController @RequestMapping(“demo”) public class DemoController { 4 @Autowired
private DemoService demoService;
7
8 @GetMapping(“test”
9 )
public String test() {
return demoService.test(); } }
再次运行BetaWebApplication类中的main方法启动项目,发现如下报错
APPLICATION FAILED TO START ***************************
4
Description:
Field demoService in com.yibao.beta.web.controller.DemoController required a bean of type 'com.yit 7
Action:
Consider defining a bean of type ‘com.yibao.beta.biz.service.DemoService’ in your configuration.
原因是找不到DemoService类,此时需要在BetaWebApplication入口类中增加包扫描,设置@SpringBootApplication注解中 的scanBasePackages值为com.yibao.beta,最终如下所示
package com.yibao.beta.web;
2
@SpringBootApplication(scanBasePackages = “com.yibao.beta” )
@MapperScan(“com.yibao.beta.dao.mapper”) public class BetaWebApplication {
7
public static void main(String[] args) { SpringApplication.run(BetaWebApplication.class, args)
;
} }
设置完后重新运行main方法,项目正常启动,访问http://localhost:8080/demo/test得到如下效果
• • • . localhost:8080/demo/test X +
<–> C © localhost:8080/demo/test
test
6.集成Mybatis
父pom文件中声明mybatis-spring-boot-starter及lombok依赖
dependencyManagement>
(dependencies
(dependency
org.mybatis.spring.boot</groupId
mybatis-spring-boot-starter</artifactId
1.3.2</version
〈/dependency
(dependency
org.projectlombok</groupId
lombok</artifactId
1.16.22</version
〈/dependency
「dependencies epen e nc e s
在beta-dao层中的pom文件中添加上述依赖
<dependency
mybatis-spring-boot-starter</artifactId
〈/dependency
9 >
(dependency
11 >
mysql</groupId
13 >
mysql-connector-java</artifactId
</dependency
(dependency
org.projectlombok</groupId
lombok</artifactId
</dependency
在beta-dao层创建com.yibao.beta.dao包,通过mybatis-genertaor工具生成dao层相关文件(DO、Mapper、xml),存放目 录如下
applicatio.properties文件添力口jdbc及mybatis相应配置项
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://192.168.1.1/test?useUnicode=true&characterEncoding=utf-8 spring.datasource.username = test spring.datasource.password = 123456
5
mybatis.mapper-locations = classpath:mybatis/*.xml
mybatis.type-aliases-package = com.yibao.beta.dao.entity
8
DemoService通过@Autowired注解注入UserMapper,修改口6巾。56口忆6的test方法使之调用UserMapper的
selectByPrimaryKey方法,最终如下所示
package com.yibao.beta.biz.service.impl;
@Service public class DemoServicelmpl implements DemoService {
@Autowired private UserMapper userMapper;
9 ©Override public String test()
{
UserDO user = userMapper.selectByPrimaryKey(l) ;
return user.toString();
再次运行BetaWebApplication类中的main方法启动项目,发现如下报错
APPLICATION FAILED TO START
3
Description:
Field userMapper in com.yibao.beta.biz.service.impl.DemoServiceImpl required a bean of type 'com.)
6 .
7
8
Action:
Consider defining a bean of type ‘com.yibao.beta.dao.mapper.UserMapper’ in your configuration.
原因是找不到UserMapper类,此时需要在BetaWebApplication入口类中增加dao层包扫描,添加@MapperScan注解并设置其 值为com.yibao.beta.dao.mapper,最终如下所示
package com.yibao.beta.web;
2
@SpringBootApplication(scanBasePackages = “com.yibao.beta” )
@MapperScan(“com.yibao.beta.dao.mapper”) public class BetaWebApplication {
7
public static void main(String[] args) { SpringApplication.run(BetaWebApplication.class, args)
; } }
设置完后重新运行main方法,项目正常启动,访问http://localhost:8080/demo/test得到如下效果
至此,一个简单的SpringBoot+Mybatis多模块项目已经搭建完毕,我们也通过启动项目调用接口验证其正确性。
四、总结
一个层次分明的多模块工程结构不仅方便维护,而且有利于后续微服务化。在此结构的基础上还可以扩展common层(公共组 件)、server层(如dubbo对外提供的服务)微信搜索web_esouce获取更多推送
此为项目重构的第一步,后续还会的框架中集成logback、disconf、redis、dubbo等组件
五、未提到的坑
在搭建过程中还遇到一个maven私服的问题,原因是公司内部的maven私服配置的中央仓库为阿里的远程仓库,它与maven自 带的远程仓库相比有些jar包版本并不全,导致在搭建过程中好几次因为没拉到相应jar包导致项目启动不了。
-END-
如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下方二维码 即可进入无广告交流群。
1扫描二维码进群1
8久一&
扫一扫上面的二维码图案,加我微信
推荐阅读
1 Java后端优质文章整理
2 . I DEA 远程一键部署 Spring Boot 到 Docker
3 .这26条,你赞同几个?
4 . 7个开源的Spring Boot前后端分离项目
5 .如何设计API接口,实现统一格式返回?
Java后端
长按识别二维码,关注我的公众号
喜欢文章,点个在看
阅读原文
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
Spring Boot + Mybatis配合AOP和注解实现动态数据源切换配置
韩数 Java后端 2019-10-25
点击上方Java后端,选择设为星标
技术博文,及时送达
来自|韩数
链接 | juejin.im/post/5d830944f265da03963bd153
0、前言
随着应用用户数量的增加,相应的并发请求的数量也会跟着不断增加,慢慢地,单个数据库已经没有办法满足我们频繁的数据库 操作请求了。
在某些场景下,我们可能会需要配置多个数据源,使用多个数据源(例如实现数据库的读写分离)来缓解系统的压力等,同样 的,Springboot官方提供了相应的实现来帮助开发者们配置多数据源,一般分为两种方式(目前我所了解到的),分包和AOP。
而在Springboot+Mybatis实现多数据源配置中,我们实现了静态多数据源的配置,但是这种方式怎么说呢,在实际的使用中不 够灵活,为了解决这个问题,我们可以使用上文提到的第二种方法,即使用AOP面向切面编程的方式配合我们的自定义注解来实现 在不同数据源之间动态切换的目的。
1、数据库准备
数据库准备仍然和之前的例子相同,具体建表sql语句则不再详细说明,表格如下:
数据库 testdatasourcel testdatasource2
sys_user
y
s
u
5
e
r
2
u
s
e
r
i
d
(
i
n
t
)
r
u
ys_u5er2 user_id(int)r u
ysu5er2userid(int)ruer_name(varchar) 'user_age (int) 同
并分别插入两条记录,为了方便对比,其中testdatasourcel为芳年25岁的张三,testdatasource2为芳年30岁的李四。
2、环境准备
首先新建一个Springboot项目,我这里版本是2.1.7.RELEASE,并在pom文件中引入相关依赖,和上次相比,这次主要额外新增 了aop相关的依赖,如下:
〈dependency〉
mysql
mysql-connector-java
〈/dependency〉
org.springframework.boot
spring-boot-starter-jdbc
々dependency〉
(dependency〉
spring-aop
5.1.5.RELEASE
〈/dependency〉
〈dependency〉
j unit
j unit
〈/dependency〉
(dependency〉
org.aspectj
aspectjweaver
1.9.2
々dependency〉
3、代码部分 首先呢,在我们Springboot的配置文件中配置我们的datasourse,和以往不一样的是,因为我们有两个数据源,所以要指定相 关数据库的名称,其中主数据源为primary,次数据源为secondary如下:
#配置主数据库
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/testdatasource1?useUnicode=true&ch
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
6
##配置次数据库
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/testdatasource2?useUnicode=true&c
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
12
13
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
需要我们注意的是,Springboot2.0在配置数据库连接的时候需要使用jdbc-url,如果只使用url的话会报jdbcUrl is required with driverClassName.错误。
新建一个配置文件,DynamicDataSourceConfig用来配置我们相关的bean,代码如下
©Configuration
@MapperScan(basePackages = “com.mzd.multipledatasources.mapper”, sqlSessionFactoryRef = "SqlSessi public class DynamicDataSourceConfig {
4
//将这个对象放入Spring容器中
@Bean(name = “PrimaryDataSource”)
//表示这个数据源是默认数据源
©Primary
//读取application .properties中的配置参数映射成为一^个对象
// prefix表示参数的前缀
@ConfigurationProperties(prefix = “spring.datasource.primary”)
public Datasource getDateSource1() {
return DataSourceBuilder.create().build(); }
@Bean(name = “SecondaryDataSource”)
@ConfigurationProperties(prefix = “spring.datasource.secondary”)
public DataSource getDateSource2() {
return DataSourceBuilder.create().build(); }
@Bean(name = “dynamicDataSource”)
public DynamicDataSource DataSource(@Qualifier(“PrimaryDataSource”) DataSource primaryDataSou @Qualifier(“SecondaryDataSource”) DataSource secondaryDat
〃这个地方是比较核心的targetDataSource集合是我们数据库和名字之间的映射
Map<Object, Object〉 targetDataSource = new HashMap<>();
targetDataSource.put(DataSourceType.DataBaseType.Primary, primaryDataSource);
targetDataSource.put(DataSourceType.DataBaseType.Secondary, SecondaryDataSource);
DynamicDataSource dataSource = new DynamicDataSource。;
dataSource.setTargetDataSources(targetDataSource);
dataSource. setDefaultTargetDataSource(primaryDataSource); //设置默认对象 return dataSource;
}
@Bean(name = “SqlSessionFactory”)
public SqlSessionFactory SqlSessionFactory(@Qualifier(“dynamicDataSource”) DataSource dynamict throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath*:mapping//.xmJ return bean.getObject();
}
而在这所有的配置中,最核心的地方就是DynamicDataSource这个类了,DynamicDataSource是我们自定义的动态切换数据源 的类,该类继承了AbstractRoutingDataSource 类并重写了它的determineCurrentLookupKey()方法。
AbstractRoutingDataSource类内部维护了一个名为targetDataSources的Map,并提供的setter方法用于设置数据源关键字与 数据源的关系,实现类被要求实现其determineCurrentLookupKey()方法,由此方法的返回值决定具体从哪个数据源中获取连 接。同时AbstractRoutingDataSource类提供了程序运行时动态切换数据源的方法,在dao类或方法上标注需要访问数据源的关 键字,路由到指定数据源,获取连接。
DynamicDataSource代码如下:
public class DynamicDataSource extends AbstractRoutingDataSource {
2
@Override
protected Object determineCurrentLookupKey() {
DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
return dataBaseType; }
DataSourceType类的代码如下:
public class DataSourceType {
2
〃内部枚举类,用于选择特定的数据类型
public enum DataBaseType { Primary, Secondary
}
7
//使用ThreadLocal保证线程安全
private static final ThreadLocal TYPE = new ThreadLocal();
10
//往当前线程里设置数据源类型
public static void setDataBaseType(DataBaseType dataBaseType) { if (dataBaseType == null) {
throw new NullPointerException(); }
TYPE.set(dataBaseType);
}
18
//获取数据源类型
public static DataBaseType getDataBaseType() {
DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Primary : TYPE.get(); return dataBaseType;
}
24
//清空数据类型
public static void clearDataBaseType() { TYPE.remove();
}
29
}
接下来编写我们相关的Mapper和xml文件,代码如下:
©Component
@Mapper
public interface PrimaryUserMapper {
List findAll();
}
©Component
@Mapper
public interface SecondaryUserMapper {
List findAll():
}
Java后端
长按识别二维码,关注我的公众号
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
Spring MVC+Spring+MyBatis实现支付宝扫码支付功能(图文详解)
纪莫 Java后端 2019-11-13
14 »/
15 public class AlipayConfig {
16 请在这里配置您的基本信息
17 //应用ID,您的APPID,收款型,既是纪的APPID对应支付宝聚号
L8 public static Stringropp_id|= “2016090900468822”;
19 .
20 //商户私钥,您的P"宇梭式RM?乱铜
21 public static String |merc6ar?t ptita亡e he>| = "MIIEvQIBADANBgkqhkiG9w0BAQEF
22
23 // 支付宝公铜.查看地斗:httny//cnpnhcmp alipay.com/platform/keyManage一htm 7
24 public static String alipay publijkey |= “MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8Ar, 25
26 //,务器异步通知页面和至产十叫〃格式的完整路径,不能加?id=123这类自定义参数
27 public static String notify_urL - “http://localhost:8080/alipay.trade.page.
28 =””
29 //页面跳转同步通知页惮住血卬:〃格式的完整路径,不能加?id=123这类自定义参专
public static String return_urL = “http://localhost:8080/alipay.trade.page.
31 //签名方式 ——
32 public static String sign_type - MRSA2”;
33 〃字存房码格式
34 public static String charset = "utf-8**;
35 //支付宝网关 I 1
public static Strind gatewayUrl 卜"https://openapi.alipaydev.com/gateway.de
37 //支付宝网关 1 1
38 public static String Log_path = wd:\test\pay\w;
39 清在这里配置您的基本信息小溪—宁静而致远©2017
readme.txt请好好看一下。
只有一个Java配置类,其余都是JSP。
3.配置AUpayConfig ⑴注册蚂蚁金服开发者账号(免费,不像苹果会收取费用) 注册地址:https://open.alipay.com ,用你的支付宝账号扫码登录,完善个人信息,选择服务类型(我选的是自研)。
⑵设置app_id和gatewayUrl
r? ▼
® ▼ 0 X •二匕 二 O ▼ ▼
区 AlipayConfig.java 区
唯长期稳定,每周日中午12点至每周一中午12点沙箱环境进行维护,期间可能出现不可£
//应用ID,好的赢石丁丽丽厩是绦的APPIDk应支付宝账号 public static String app_id = -2016090900468822";
诋❶
必看部分
APPID ❶
18
19
//支付宝回关
//商户私必然的PKCS8格式RSA2私仍
static String merchant_private_key - NMIIEvQIBADANBgkqhkiG9wOB
// 支付宝公铜,查看地址,https://openhone. alipay.coa/platform/keyHanage public static String atipay_pubLic_key = "MIIBIjANBgkqhkiG9w0BAQEFAA£
//图务器异步通知页面谿径需http:〃格式的完整路径,不能加?id=1.23这类自定 public static ing notify_urL = "http://localhost:8080/alipay.trade
//页面
同步通知页面路径需http:〃格式的完整路径,不能加?id=123这类自 static String return_urL « "http://localhost:8080/alipay.trade
public static String sign_type = “RSA2”;
//字符编码格式 一
public static String charset = “utf-8”;
//支付宝网关
pnhHr fCtic. String gateMoyUrl = "https://openapi.alipaydev.coni/gate
public static String Log_path = “d:\test\pay\”;
小溪_宁静而致远©2017
其中密钥需要自己生成,appID和支付宝网关是已经给好的,网关有dev字样,表明是用于开发测试。
⑶设置密钥
育技术同五点我
APPID O
2016090900468822
支付宝网关O
cd cUr\d,M,、,z«z»
开发者要保证接口中使用的私钥与此处的公钥脸,否则无法调用接口,可—住成方法」沙箱支付宝公钥与线上不同,清更换代码中配・;
RSA2(SHA256陛钥(推荐回 查看应用公钥|查看支付宝公钥
小溪—宁静而致远©2017
点击“生成方法”,打开界面如下:
下周密钥生成工具,解压打开后,选择2048位生成密钥:
如果没有设置过,此时显示文本是"设置应用公钥",我这里是已经设置过得。
必看部分
APPID O 2016090900468822
支付宝网关❶ https://openapi.alipaydev.com/gateway.do
RSA2(SHA256度钥(膈)❶ 杳看应用公钥|查看支付宝公钥
小溪—宁静而致远©2017
设置方法,"打开密钥文件路径”:
复制应用公钥2048.txt中的内容到点击”设置应用公钥”的弹出框中,保存:
应用公钥(SHA256withRsa)
❷ 使用SHA256withRsa ,支付宝会用SHA256withRsa算法进行接口调用时的
方法
MIIBIjANBgkqhkiG9wOBAQEFAAOCAQ8AMIIBCgKCAQEAn7IQghEEJMSO. Pv8ks2qKOsgYndSNcMd1+ByDCxNFBkuBBBTI+mNIGf7LoYxFe74raNx4k2 CZnklZ9g5aJJjNkFVtRjswzvK9ObpqSfMgj2zwPPaA6/4uHh5ccdbHRM0JPS edW4rBs05UpCs40DS+W7uA33Dg5KwJMS5EgliPUJI74CZhDMvJhWYh2iJ wDsulBRKKsU6mAcAOI+M2RiA3SBgJwlDAQAB
验证公钥正确性>>
近 ©2017
• 商户私钥(merchant_private_key)
复制 应用私钥2048.txt中的内容到merchant_private_key中。
•支付宝公钥(alipay_public_key)
2016090900468822
https://openapi.alipaydev.com/gateway.do
小溪一宁静而致远©2017
点击如上图链接,复制弹出框里面的内容到alipay_public_key。
如果这个设置不对,结果是:支付成功,但是验签失败。
如果是正式环境,需要上传到对应的应用中:
快速入门
上传应用公钥并获取支付宝公钥
更新时间:2017/01/04 访问次数:44807
上传应用公钥并获取支付…
阅读角色:支付宝账号管理者
使用应用私钥生成请求签名
⑷服务器异步通知页面路径(notify_url)
如果没有改名,修改IP和端口号就可以了,我自己的如下:
http://localhost:8080/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp
⑸页面跳转同步通知页面的路径(return_url)
http://localhost:8080/alipay.trade.page.pay-JAVA-UTF-8/return_url.jsp
4.测试运行
测试用的支付宝买家账户可以在"沙箱账”这个页面可以找到:
支付成功后,验签结果:
问题解决
由于我们使用的是沙箱测试环境,测试环境和正式上线的环境的网关是不一样的,如果配置错误,会出现,appid错误的问题。
配置如下:
源代码下载,可以关注微信公众号Java后端回复扫码即可获取。
将支付宝支付整合到ssm框架
1、项目架构
• 项目架构:spring+springmvc+mybatis
• 数据库:mysql
• 部署环境:tomcat9.0
• 开发环境:jdk9、idea
• 支付:支付宝、微信 整合到ssm一样,我们需要像沙箱测试环境一样,需要修改支付的配置信息
2、数据库代码
主要包括以下的数据库表:
• user:用户表
• order:支付产生的订单
• flow:流水账
• product:商品表:用于模拟购买商品。
drop table if exists user;
/================================/
- Table: user *
/================================/ create table user
(
id varchar(20) not null,
username varchar(128),
sex varchar(20), primary key (id)
);
alter table user comment ‘用户表’;
CREATE TABLE ‘flow’ (
‘id’ varchar(20) NOT NULL,
‘flow_num’ varchar(20) DEFAULT NULL COMMENT ‘流水号’,
‘order_num’ varchar(20) DEFAULT NULL COMMENT ‘订单号’,
’ product_id’ varchar(20) DEFAULT NULL COMMENT ‘产品主键ID’,
‘paid_amount’ varchar(11) DEFAULT NULL COMMENT ‘支付金额’,
‘paid_method’ int(11) DEFAULT NULL COMMENT ‘支付方式\r\n 1:支付宝\r\n 2:微信’,
’ buy_counts’ int(11) DEFAULT NULL COMMENT ‘购买个数’,
’ create_time’ datetime DEFAULT NULL COMMENT ‘创建时间’, PRIMARY KEY (‘id’)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘流水表’;
CREATE TABLE ‘orders’ (
‘id’ varchar(20) NOT NULL,
‘order_num’ varchar(20) DEFAULT NULL COMMENT ‘订单号’,
‘order_status’ varchar(20) DEFAULT NULL COMMENT ‘订单状态\r\n 10:待付款\r\n 20:已付款’,
‘order_amount’ varchar(11) DEFAULT NULL COMMENT ‘订单金额’,
‘paid_amount’ varchar(11) DEFAULT NULL COMMENT ‘实际支付金额’,
’ product_id’ varchar(20) DEFAULT NULL COMMENT ‘产品表外键ID’,
’ buy_counts’ int(11) DEFAULT NULL COMMENT ‘产品购买的个数’,
’ create_time’ datetime DEFAULT NULL COMMENT ‘订单创建时间’,
’ paid_time’ datetime DEFAULT NULL COMMENT ‘支付时间’,
PRIMARY KEY (‘id’)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘订单表’;
CREATE TABLE ‘product’ (
‘id’ varchar(20) NOT NULL,
‘name’ varchar(20) DEFAULT NULL COMMENT ‘产品名称’,
’ price’ varchar(11) DEFAULT NULL COMMENT ‘价格’,
PRIMARY KEY (‘id’)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘产品表’;
3、~3。数据接口层 这里就不介绍了,这个只包括简单的curd,可以使用’通用mapper’,或者’逆向工程’就行。以订单order为例给出:
public interface OrdersMapper {
int countByExample(OrdersExample example);
int deleteByExample(OrdersExample example);
int deleteByPrimaryKey( String id);
int insert(Orders record);
int insertSelective( Orders record);
List selectByExa mple (OrdersExample example);
Orders selectByPrimaryKey (String id);
int updateByExampleSelective(@Param(“record”) Orders record, @Param(“example”) OrdersExample example);
int updateByExample(@Param(“record”) Orders record, @Param(“example”) OrdersExample example);
int updateByPrimaryKeySelective( Orders record);
int updateByPrimaryKey (Orders record);
}
4、service层
同上,最后在项目源代码里可见。以订单order为例给出:
*订单操作service @author ibm
*
*
public interface OrdersService {
*
- 新增订单
@param order
public void saveOrder(Orders order);
*
*
@Titl : OrdersService.java
@Package com. sihai.service
@Descriptio :修改叮当状态,改为支付成功,已付款;同时新增支付流水
- Copyright: Copyright © 2017
- CompanyFURUBOKE. SCIENCE.AND. TECHNOLOGY
@author sihai
@date 2017年8月23日下午9:04:35
@version V1.0
*
public void updateOrderStatus(String orderld, String alpayFlowNum, String paidAmount);
*
- 获取订单
@param orderd
@return
public Orders getOrderById(String orderld);
}
4、支付宝支付controller (支付流程)
支付流程图
首先,启动项目后,输入http:〃localhost:8080/,会进入到商品页面,如下
G| 命 | ① Iocalhost8080
[
*最常访问 包火狐官方站点 ■新手上路 包常用网址E3京东商城 中最常访问 白火孤官方站点 产品编号产品名称产品价格操作
1 辣条 0.01 购买
2 面包 0.02 购买
下面是页面代码
商品页面(products.jsp)
▼ ■ WEB-INF
▼・)印
耨 a I i pay Su ccess j sp
耨 goConfirmjsp
[gjgoPay.jsp
患 payQrCodejsp
耨 paySuccessjsp
患 productsjsp
招 user.jsp
…@web.xml .
https:/iJbTgisdn. net/sihai 12345
代码实现:
产品编号 | 产品名称 | 产品价格 | 操作 |
${p.id } | ${p.name } | ${p.price } | 购买 |
填写个数,然后点击生成订单,调用如下代码
/**
*分段提交
©RequestMapping(value = z createOrder")
©ResponseBody
public LeeJSONResult createOrder(Orders order) throws Excejgtion {
Product p = productService. getProductByld(order. getProductldO);
String orderld = sid. nextShort();
order, setld(orderld);
order. setOrderNum(orderld):
order. setCreateTime(new Date());
order, set Order Amount (String. valueOf(^ 1 oat. valueOf^. getPrice ()) * order. getBuyCounts ()));
order. setOrderStatus (OrderStatusEnum. WAIT_PAY. key);
orderService. saveOrder(order);
return LeeJSONResult. oA’Corderid);
} https://blog. csdn. net/sihai12345
根据SID (生成id的工具)等信息生成订单,保存到数据库。
Tips:可以关注微信公众号:Java后端,获取更多类似技术博文推送。
进入到选择支付页面
调用了如下代码:
/**
- 分段提交
- 第二段
- @param orderld
- ©return
- ©throws Exception
*/
@RequestMapping(value = goPay")
public ModelAndView goPay(String orderld) throws Exception {
Orders order = orderService. getOrderByld(orderld);
Product p = productservice. getProductByld(order. getProductldO);
ModelAndView mv = new ModelAndView( viewName: rgoPay" );
mv. addObject ( attributeName: order \ order);
mv. addObject ( attributeName: p z, p);
return mv;
} https: I blog. csdn. net si ha i 12345
然后,我们选择支付宝支付,进入到了我们支付的页面了,大功告成!
调用了如下代码:
@RequestMapping(va山e = “/goAlipay”, produces = “text/html; charset=UTF-8”)
@ResponseBody
public String goAlipay(String orderId, HttpServletRequest request, HttpServletRequest response) throws Exception {
Orders order = orderService.getOrderById(orderId);
Product product = productService.getProductById(order.getProductId());
〃获得初始化的AlpayQient
AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_ke:
〃设置请求参数
AlipayT radePagePayRequest alipayRequest = new AlipayT radePagePayRequest();
alipayRequest.setReturnUrl(AlipayConfig.return_url);
alipayRequest.setNotifyUrl(AlipayConfig.notify_url);
〃商户订单号,商户网站订单系统中唯一订单号,必填
String out_trade_no = orderId;
〃付款金额,必填
String total_amount = order.getOrderAmount();
〃订单名称,必填
String subject = product.getName();
〃商品描述,可空
String body =“用户订购商品个数:”+ order.getBuyCounts();
“该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m〜15d° m-分钟,h-小时,d-天, lc-当天 (1c-当天的情况下,无论交易何时£ String timeout_express = “1c”;
alipayRequest.setBizContent(“{“out_trade_no”:”“+ out_trade_no +”“,”
- ““total_amount”:”“+ total_amount +”“,”
- ““subject”:”“+ subject +”“,”
- ““body”:”“+ body +”“,”
- ““timeout_express”:”“+ timeout_express +”“,”
- ““product_code”:“FAST_INSTANT_TRADE_PAY”}”);
//请求
String result = alipayClient.pageExecute(alipayRequest).getBody();
return result;
}
“1 2d
这段代码都可以在阿里支付的demo里面找到的,只需要复制过来,然后改改,整合到ssm环境即可。
上面就是将阿里支付宝支付整合到ssm的全过程了。本文参考了博主「小溪_宁静而致远」的支付教程文章,欢迎搜索博主关注。
-END-
如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下方二维码 即可进入无广告交流群。
J扫描二维码进群1
推荐阅读
1 Spring Boot + PageHelper实现多数据源并分页
2 . Spring Boot微信点餐系统
3 .Spring MVC至U Spring Boot的简化之路
4 12306的架构到底有多牛逼?
5 .团队开发中Git最佳实践
喜欢文章,点个在看
阅读原文
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
【MyBatis】写了 10年的代码,我最怕写MyBatis这些配置,现在有详解了
Java后端2月28日
作者|阿进的写字台
编辑|公众号(Java后端)
链接 | cnblogs.com/homejim/p/9782403.html
在使用mybatis过程中,当手写JavaBean和XML写的越来越多的时候,就越来越同意出错。这种重复性的工作,我们当然 不希望做那么多。
还好,mybatis为我们提供了强大的代码生成–MybatisGenerator。
通过简单的配置,我们就可以生成各种类型的实体类,Mapper接口,MapperXML文件,Example对象等。通过这些生成的文 件,我们就可以方便的进行单表进行增删改查的操作。
Tips:关注微信公众号:Java后端,获取每日推送。
以下的工具使用的都是IDEA
1.1 创建Maven项目
1.1.1 菜单上选择新建项目
File | New | Project
1.1.2 选择左侧的Maven
H New Project X
Project
D
I
G
1
.
3
v
e
r
s
i
o
n
▼
N
e
w
.
.
.
C
r
e
a
t
e
f
r
o
m
a
r
c
h
e
t
y
p
«
A
d
d
A
r
c
h
e
t
y
p
e
一►
7
o
m
.
a
T
′
3
S
s
i
a
n
.
−
r
i
3
>
e
′
.
a
r
c
h
e
t
−
,
p
<
s
:
b
a
m
b
o
o
−
p
l
u
g
i
n
−
a
r
c
h
e
t
y
p
e
►
l
o
m
.
a
:
a
s
s
i
a
n
.
n
a
,
−
:
−
.
3
r
c
h
€
?
/
p
e
s
:
c
o
n
f
l
u
e
n
c
e
−
p
l
u
g
i
n
−
a
r
c
h
e
t
y
p
e
►
n
i
a
v
c
n
r
c
h
e
t
;
,
p
c
s
j
i
r
a
−
p
l
u
g
i
n
−
a
r
c
h
e
t
y
p
e
►
c
o
m
.
r
f
c
j
n
a
v
e
n
.
a
r
c
h
e
t
y
p
e
5
j
p
a
−
m
a
v
e
n
−
a
r
c
h
e
t
y
p
e
►
d
e
a
k
q
u
i
n
e
t
.
j
b
o
′
i
t
c
c
j
b
o
DIG ^1.3 version ▼ New... Create from archetyp« Add Archetype一 ► 7om.aT'3Ssian.-ri3> e'.archet-,p<s:bamboo-plugin-archetype ► lom.a: assian.na,-:-.3rch€?/pes:confluence-plugin-archetype ► niavcn^rchet;, pcs jira-plugin-archetype ► com.rfcjnaven.archetype5 jpa-maven-archetype ► deakquinet.jbo'itcc jbo
DIG1.3version▼New...Createfromarchetyp«AddArchetype一►7om.aT′3Ssian.−ri3>e′.archet−,p<s:bamboo−plugin−archetype►lom.a:assian.na,−:−.3rch€?/pes:confluence−plugin−archetype►niavcnrchet;,pcsjira−plugin−archetype►com.rfcjnaven.archetype5jpa−maven−archetype►deakquinet.jbo′itccjbo
c
c
−
cc-
cc−eam-archetype
► netdatabinde: data-app
► netliftwcc; lift-archetype-basic
► netl(ftwcb:lift-archetype- blank
► net.sfrnavefi-bn^maven-archetype-har
〉 一, -maven-orchetype-s«r
► q apaJ - I io- camel-archetype-activefnq
► ^rg.apache.came .archetype.xamd-archetype-component
I* ’ ,•一 camel-archetype java
► org.apachc.camc archetypt: camel-archetypc-scala ► org^f ’ c^mel-archetype-spnng
► orgqpacheqmdarchetypemel-archetype-war
► orgxipache.cocoon:cocoon-22-archetype-block
► org.apaehe.cocoon cocoon-22-archetype-block-plain
► org.apache.cocoon cocoon-22-archetype-webapp
► : rg^jpache.mavenujrchetyp’" , :maven-archetype-j2ee-simple
► rg^pache.maven.archet .i)".:mav€n-archrtype-marmalade-mojo
► 1 g.apache.ni.11.-1. .T< het.: - maven-archetype-mojo
► org.apache.ma^en.archetypesimaven-archetype-portiet
► org.apache.mavcn.archetype;:maven-archetype-profiles
:―)一”E■喷诙愉 esdn. net/weixin 37139197
► rg.apacre.mdven^.v< hrtyp maven-archetype-srte
由于我们只是创建一个普通的项目,此处点击Next即可。
1.1.3 输入GroupId和ArtifactId
•在我的项目中,
GroupId 填 com.homejim.mybatis
ArtifactId 填 mybatis-generator
点击Next。
1.1.4 Finish
通过以上步骤,一个普通的Maven项目就创建好了。
1.2 配置 generator.xml
其实名字无所谓,只要跟下面的pom.xml文件中的对应上就好了。
-K Compare With… Ctrl+D Database Changes tGeneia-
Compare File with Editor Designer
? Compare with Clipboard Event Log 的配置-
Quick Switch Scheme… Ctrl Image Layers
Toolbar
'成— 4 山蚌甲
View Navigate Code Analyze Refactor Build Run Tools VCS Window Hei J
1.4.2 Maven Projects 中双击 mybatis-generator
在右侧此时可以看到Maven Projects 了。找到mybatis-generator插件。
mybatis-generator | Plugins | mybatis-generator | mybatis-generator
▼ 1T mybatis-generator
►% Lifecycle
▼ Bg Plugins
► iS clean (org.apache.rrj ► compiler (org.apach ► [^deploy (org.apache. ► (^install (org.apache.n ►焦jar (org.apache.mav ▼儒 mybatis-generator f
R mybati s - g en eratc R mybati s - g en eratc
► 儒 resources (org.apac
► 薛 site (org.apache.ma1 ► surefire (org.apache
1.4.3双击运行
运行正确后,生成代码,得到如下的结构
♦彳 mybatis-generator C:\Users\Administrator\Doi
► ■ .idea
▼ ■ src
▼・ main
T ・java
▼ EB com.homejim.mybatis
▼ Z entity
④ Blog
@ Blog Example
▼ > mapper
❶ BlogMapper
▼跑 resources
▼ B mybatis.mapper
患 BlogMapperjcml
翡 generatonxml
► ・ test
jf_ mybatis-generator.iml
/7) pom.xml
仅仅是上面那么简单的使用还不够爽。那么我们就可以通过更改generator.xml配置文件的方式进行生成的配置。
2.1 文档
推荐查看官方的文档。
英文不错的:http://www.mybatis.org/generator/configreference/xmlconfig.html
中文翻译版:http://mbg.cndocs.ml/index.html
2.2 官网没有的
2.2.1 property 标签
该标签在官网中只是说用来指定元素的属性,至于怎么用没有详细的讲解。
2.2.1.1 分隔符相关
〈property name=“autoDelimitKeywords” value=“true”/>
〈property name=“beginningDelimiter” value=“'”/>
〈property name=“endingDelimiter” value="’ "/>
以上的配置对应的是mysql,当数据库中的字段和数据库的关键字一样时,就会使用分隔符。
比如我们的数据列是delete,按以上的配置后,在它出现的地方,就变成’delete’。
2.2.1.2 编码
默认是使用当前的系统环境的编码,可以配置为GBK或UTF-8。
〈property name=“javaFileEncoding” value=“UTF-8”/>
我想项目为UTF-8,如果指定生成GBK,则自动生成的中文就是乱码。
2.2.1.3 格式化
〈property name="javaFormatter“ value=“org.mybatis.generator.api.dom.DefaultJavaFormatter”/>
<property name="xmlFormatter“ value=“org.mybatis.generator.api.dom.DefaultXmlFormatter”/>
这些显然都是可以自定义实现的的。
2.2.2 plugins 标签
plugins标签用来扩展或修改代码生成器生成的代码。
在生成的XML中,是没有这个标签的。该标签是配置缓存的。
如果我们想生成这个标签,那么可以plugins中进行配置。
〈p山gin type=“org.mybatis.generator.p山gins.CacheP山gin” >
〈/p仙gin>
,paghe eviction="LRU”>
<! —
宵 ARNING - ®mbg generated
’ Illis element is auton|atically generated by MyBatis Genexatox. do not modify.
£ https://blog. csdn. net/weixin_37139197
/—h.0
比如你想生成的JavaBean中自行实现Serializable接口。
还能自定义插件。
这些插件都蛮有用的,感觉后续可以专门开一篇文章来讲解。
看名称,就知道是用来生成注释用的。
默认配置:
〈property name=“suppressA11comments” value=“false”/>
〈property name=“suppressDate” value=“false”/>
〈property name=“addRemarkComments” value=“false”/>
suppressAllComments:阻止生成注释,默认值是false。
suppressDate:阻止生成的注释包含时间戳,默认为false。
addRemarkComments:注释中添加数据库的注释,默认为false。
还有一个就是我们可以通过 type 属性指定我们自定义的注解实现类,生成我们自己想要的注解。自定义的实现类需要实
现 0rg.mybatis.generator.api.CommentGenerator。
2.2.4 源码
https://github.com/homejim/mybatis-cn
E homejirn / mybatis-cn ® Watch» 2 | * Unstar | 16 ] 曾 | 4
<> Code ① Issues 0 fl Pull requests 0 ■ Projects 0 EK Wiki 通 Insights * Settings
mybatis源码的中文J博以及mybatis的使用和源码解析 Edit
mybatis mybatis-sources mybatis-Chinese Manage topics
® 83 commits 炉 1 branch 4 0 releases 211 contributor 杀 View license
| Branch: master- New poll request Create new file Lfplosd files j Find file
本文作者:阿进的写字台,原文链接:cnblogs.com/homejim/p/9782403.html
由公众号Java后端)编辑,转载请著名本句话及原文出处。
-END-
如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「web_resource」,欢迎添加小编微信「focusoncode」,每 日朋友圈更新一篇高质量技术博文(无广告)。
推荐阅读
1浅谈Web网站架构演变过程
2. 一个非常实用的特性,很多人没用过
3.Spring Boot 2.x操作缓存的新姿势
4 MyBatis中SQL写法技巧小总结
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
【小技巧】MyBatis中SQL写法技巧小总结
koala Java后端2月27日
最近有个兄弟在搞mybatis,问我怎么写sql ,说简单一点巾丫53甘5就是写原生sql,官方都说了 mybatis的动态sql语句是基于 OGNL表达式的。可以方便的在sql语句中实现某些逻辑.总体说来mybatis动态SQL语句主要有以下几类:
- if语句(简单的条件判断)
- choose (when,otherwize),相当于java 语言中的 switch ,与 jstl 中的choose 很类似.
- trim (对包含的内容加上prefix,或者suffix等,前缀,后缀)
- where (主要是用来简化sql语句中where条件判断的,能智能的处理and or ,不必担心多余导致语法错误)
- set (主要用于更新时)
- foreach (在实现mybatis in语句查询时特别有用)
我说一下:if when都仅仅对于Map类型的才能进行判断,Integer, String那些都不能进行判断,虽然说mybatis最后都是把参 数封装为一个Map集合再通过占位符接入的。另一个就是trim很强大,可以说where和set能干的他都能干。choose在进行”单 个“模糊查询时候很方便。
往期优质技术文章可以关注徵信公众号:Java后端,后台回复技术博文获取。
下面分别介绍这几种处理方式 - mybaits if语句处理
〈select id=“dynamiclfTest” parameterType=“Blog” resultType=“Blog”>
select * from t_blog where 1 = 1
and title = #{title}
and content = #{content}
and owner = #{owner}
这条语句的意思非常简单,如果你提供了title参数,那么就要满足title=#{title},同样如果你提供了Content和。wner的时候, 它们也需要满足相应的条件,之后就是返回满足这些条件的所有Blog,这是非常有用的一个功能,以往我们使用其他类型框架或 者直接使用JDBC的时候,如果我们要达到同样的选择效果的时候,我们就需要拼SQL语句,这是极其麻烦的,比起来,上述的 动态SQL就要简单多了。 - choose (when,otherwize),相当于java 语言中的 switch,与 jstl 中的choose 很类似
〈select id=“dynamicChooseTest” parameterType=“Blog” resultType=“Blog”>
select * from t_blog where 1 = 1
〈when test=“title != null”>
and title = {{title}
〈when test=“content != null”>
and content = {{content}
〈otherwise)
and owner = “ownerl”
〈/otherwise)
〈/select)
when元素表示当when中的条件满足的时候就输出其中的内容,跟JAVA中的switch效果差不多的是按照条件的顺序,当when中 有条件满足的时候,就会跳出choose,即所有的when和。therwise条件中,只有一个会输出,当所有的我很条件都不满足的时 候就输出otherwise中的内容。所以上述语句的意思非常简单,当title!=null的时候就输出and titlte = #{title},不再往下判断条 件,当title为空且content!=null的时候就输出and content = #{content},当所有条件都不满足的时候就输出otherwise中的内 容。
3.trim (对包含的内容加上prefix,或者suffix等,前缀,后缀)
〈select id=“dynamicTrimTest” parameterType=“Blog” resultType=“Blog”>
select * from t_blog
<if test=“title != null”)
title = #{title}
<if test=“content != null”)
and content = #{content}
<if test=“owner != null”)
or owner = #{owner}
</if)
〈/trim)
〈/select)
后缀,与之对应的属性是prefix和suffix;可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的 属性是prefixOverrides和suffixOverrides;正因为切巾有这样的功能,所以我们也可以非常简单的利用trim来代替where元素 的功能。 - where (主要是用来简化sql语句中where条件判断的,能智能的处理and or条件
〈select id=“dynamicWhereTest” parameterType=“Blog” resultType=“Blog”>
select * from t_blog
title = #{title}
and content = #{content}
and owner = #{owner}
where元素的作用是会在写入where元素的地方输出一个where,另外一个好处是你不需要考虑where元素里面的条件输出是什 么样子的,MyBatis会智能的帮你处理,如果所有的条件都不满足那么MyBatis就会查出所有的记录,如果输出后是and 开头
的,乂丫33也会把第一个and忽略,当然如果是or开头的,MyBatis也会把它忽略;此外,在where元素中你不需要考虑空格的问 题,MyBatis会智能的帮你加上。像上述例子中,如果title=null,而content != null,那么输出的整个语句会是select * from t_blog where content = #{content},而不是select * from t_blog where and content = #{content},因为乂丫33甘5会智能的 把首个and或or给忽略。
5.set (主要用于更新时)
〈update id=“dynamicSetTest” parameterType=“Blog”>
update t_blog
title = #{titie}
content = #{ontent}
owner = #{owner}
where id = #{id}
set元素主要是用在更新操作的时候,它的主要功能和where元素其实是差不多的,主要是在包含的语句前输出一个set,然后如 果包含的语句是以逗号结束的话将会把该逗号忽略,如果set包含的内容为空的话则会出错。有了set元素我们就可以动态的更新 那些修改了的字段。 - foreach (在实现mybatis in语句查询时特别有用)
foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach元素的属性主要有
item, index, collection, open, separator, close。item表示集合中每一个元素进行迭代时的别名,index指定一个名字, 用于表示在迭代过程中,每次迭代到的位置,open表示该语句以什么开始,separator表示在每次进行迭代之间以什么符号作为 分隔符,close表示以什么结束,在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的, 但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:
• 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list。
• 如果传入的是单参数且参数类型是一个3『^丫数组的时候,collection的属性值为array。
• 如果传入的参数是多个的时候,我们就需要把它们封装成一个Mapy,当然单参数也可以封装成map,实际上如果你在传入 参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传 入的List或array对象在自己封装的map里面的key。
作者:牧羊人影视
原文链接:https://blog.csdn.net/tengxing007/article/details/54864897
-END-
如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「web_resource」,欢迎添加小编微信「focusoncode」,每 日朋友圈更新一篇高质量技术博文(无广告)。
I扫描二维码添加小编1
推荐阅读
工狠!删库跑路!
2 .分布式与集群的区别究竟是什么?
3 .看完这篇HTTP,面试官就难不倒你了
4 .快速建站利器!
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
从0开始手写一个Mybatis框架,三步搞定!
我叫刘半仙Java后端1月9日
来自:开源中国,作者:我叫刘半仙 链接:my.oschina.net/liughDevelop/blog/1631006
继上一篇手写SpringMVC之后,我最近趁热打铁,研究了一下Mybatis。MyBatis框架的核心功能其实不难,无非就是动态代理 和jdbc的操作,难的是写出来可扩展,高内聚,低耦合的规范的代码。本文完成的Mybatis功能比较简单,代码还有许多需要改 进的地方,大家可以结合Mybatis源码去动手完善。
一、Mybatis框架流程简介
在手写自己的Mybatis框架之前,我们先来了解一下Mybatis,它的源码中使用了大量的设计模式,阅读源码并观察设计模式在其 中的应用,才能够更深入的理解源码(ref: Mybatis源码解读-设计模式总结http://www.crazyant.net/2022.html)。我们对 上图进行分析总结:
1、mybatis的配置文件有2类
• mybatisconfig.xml,配置文件的名称不是固定的,配置了全局的参数的配置,全局只能有一个配置文件。
• Mapper.xml配置多个statemement,也就是多个sql,整个mybatis框架中可以有多个Mappe.xml配置文件。
2、通过mybatis配置文件得到SqlSessionFactory
3、通过SqlSessionFactory得到SqlSession,用SqlSession就可以操作数据了。
4、SqlSession通过底层的Executor (执行器),执行器有2类实现:
, * Executor - org.apache.ibatis.executor
» ♦第 BaseExecutor - o|~g.apache.ibatis.executor
年 Caching Executor - org.apache.ibatis.executor
•基本实现
•带有缓存功能的实现
5、MappedStatement是通过Mapper.xml中定义statement生成的对象。
6、参数输入执行并输出结果集,无需手动判断参数类型和参数下标位置,且自动将结果集映射为Java对象
• HashMap, KV格式的数据类型
• Java的基本数据类型
• POJO, java的对象
二、梳理自己的Mybatis的设计思路
根据上文Mybatis流程,我简化了下,分为以下步骤:
1、读取xml*件,建立连接
从图中可以看出,MyConfiguration负责与人交互。待读取xml后,将属性和连接数据库的操作封装在MyConfiguration对象中 供后面的组件调用。本文将使用dom4j来读取xml文件,它具有性能优异和非常方便使用的特点。
2、创建SqlSession,搭建Configuration和Executor之间的桥梁
我们经常在使用框架时看到Session, Session到底是什么呢? 一个Session仅拥有一个对应的数据库连接。类似于一个前段请求 Request,它可以直接调用exec(SQL)来执行SQL语句。从流程图中的箭头可以看出,MySqlSession的成员变量中必须得有 MyExecutor和MyConfiguration去集中做调配,箭头就像是一种关联关系。我们自己的MySqlSession将有一^个getMapper方 法,然后使用动态代理生成对象后,就可以做数据库的操作了。
3、创建Executor,封装JDBC操作数据库
Executor是一个执行器,负责SQL语句的生成和查询缓存(缓存还没完成)的维护,也就是jdbc的代码将在这里完成,不过本文 只实现了单表,有兴趣的同学可以尝试完成多表。
4、创建MapperProxy,使用动态代理生成Mapper对象
我们只是希望对指定的接口生成一个对象,使得执行它的时候能运行一句sql罢了,而接口无法直接调用方法,所以这里使用动态 代理生成对象,在执行时还是回到MySqlSession中调用查询,最终由MyExecutor做JDBC查询。这样设计是为了单一职责,可 扩展性更强。
三、实现自己的Mybatis
工程文件及目录:
/ 理 liughi-imybatis
▲ * src/main/java
/ ■ com.liugh
/ SS bean
<?xml version="1.0" encoding="UTF-8"?>D User.java
d 器 config
D Functionjava
D MapperBean.java
/ SS mapper
[j UserMapper.java
▲ . sqlSession
{j Excutorjava
D MyConfigu ration Java
曲 MyExcutorjava
■ MyMapperProxyjava
D My Sq I session java
D D TestMybatisjava
a src/main/resources
Q config.xml
Q UserMapper.xml
% src/test/java
% src/test/resou rces
0 J RE System Library [JavaSE-1.8]
3 Maven Dependencies
» a
» target
B pom.xml
首先,新建一个maven项目,在pom.xml中导入以下依赖:
〈project xmlns=“http://maven.apache.Org/POM/4.0.0” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xsi:schemaLocation="h
4.0.0
com.liugh
liugh-mybatis
0.0.1-SNAPSHOT
jar
〈properties)
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
〈/properties)
〈dependencies)
<!一读取xml文件->
〈dependency)
dom4j
〈artifactId>dom4j〈/artifactId>
〈version>1.6.1〈/version>
〈/dependency)
<–MySQL -->
〈dependency)
〈groupId)mysql〈/groupId)
〈artifactId)mysql-connector-java〈/artifactId)
〈version)5.1.29〈/version)
</dependency)
〈/dependencies)
〈/project)
创建我们的数据库xml配置文件:
〈database)
〈property name=“driverClassName”>com.mysql.jdbc.Driver
〈property name=“url”>jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
〈property name=“username”>root
〈property name="password”>123456
〈/database)
然后在数据库创建test库,执行如下SQL语句:
CREATE TABLE ‘user’ (
’ id’ varchar(64) NOT NULL,
’ password’ varchar(255) DEFAULT NULL,
’ username’ varchar(255) DEFAULT NULL,
PRIMARY KEY (‘id’)
)ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO ‘test’.’ user’ (’ id’, ’ password’, ’ username’) VALUES (T, ‘123456’, ‘liugh’);
创建User实体类,和UserMapper接口和对应的xml文件:
package com.liugh.bean;
public class User {
private String id;
private String username;
private String password;
//省略ge t se t toString方法…
}
package com.liugh.mapper;
import com.liugh.bean.User;
public interface UserMapper {
public User getUserById (String id);
}
喜欢文章,点个在看
阅读原文
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
从零搭建一个Spring Boot开发环境! Spring Boot+Mybatis+Swagger2环境搭建
calebman Java后端 2019-11-15
优质文章,及时送达
作者 | calebman
链接 | www.jianshu.com/p/95946d6b0c7d
本文简介
• 为什么使用Spring Boot
• 搭建怎样一个环境
• 开发环境
• 导入快速启动项目
• 集成前准备
• 集成Mybatis
• 集成Swagger2
• 多环境配置
• 多环境下的日志配置
• 常用配置
为什么使用Spring Boot
Spring Boot相对于传统的SSM框架的优点是提供了默认的样板化配置,简化了Spring应用的初始搭建过程,如果你不想被众多 的xml配置文件困扰,可以考虑使用Spring Boot替代
搭建怎样一个环境
本文将基于Spring官方提供的快速启动项目模板集成Mybatis、Swagger2框架,并讲解mybatis generator一键生成代码插件、 logback、一键生成文档以及多环境的配置方法,最后再介绍一下自定义配置的注解获取、全局异常处理等经常用到的东西。
开发环境
本人使用IDEA作为开发工具,IDEA下载时默认集成了SpringBoot的快速启动项目可以直接创建,如果使用Eclipse的同学可以考 虑安装SpringBoot插件或者直接从这里配置并下载SpringBoot快速启动项目,需要注意的是本次环境搭建选择的是 SpringBoot2.0的快速启动框架,Spring Boot2.0要求jdk版本必须要在1.8及以上。
导入快速启动项目
不管是由IDEA导入还是现实下载模板工程都需要初始化快速启动工程的配置,如果使用IDEA,在新建项目时选择Spring
Initializr,主要配置如下图
IDEA新建SpringBoot项目-填写项目/包名
IDEA新建SpringBoot项目-选择依赖包
点击next之后finish之后IDEA显示正在下载模板工程,下载完成后会根据pom.xml下载包依赖,依赖下载完毕后模板项目就算创 建成功了,如果是直接从官方网站配置下载快速启动项目可参考下图配置
SPRING INITIALIZR
Generate a ,with J2
Project Metadata
Artrf«ct coordinates
Group
co*, cprutf. 4MO
Artifact
spxu>("boom aple
start spring io s powwvd by
直接下载SpringBoot快速启动项目-项目配置
从Search for dependencies框中输入并选择Web、Mysql、Mybatis加入依赖,点击66件46 Project下载快速启动项目,然 后在IDE中选择导入Maven项目,项目导入完成后可见其目录结构如下图
v spring-boot-example D:\CJH\idea-chee-workspace\spring-boot-example > .mvn
v . src ▼ main
2 java y com. spring.demo.springbootexample
SpringBootExampleApplication
v resources static
、]application.properties v* java
▼ com.spring.demo.springbootexample
c SpringBootExampleApplicationTests m .gitignore =mvnw =mvnw.cmd
I m pom.xml I
快速启动项目-项目结构
需要关注红色方框圈起来的部分,由上往下第一个java类是用来启动项目的入口函数,第二个properties后缀的文件是项目的配 置文件,第三个是项目的依赖包以及执行插件的配置
集成前准备
修改.properties为.yml
yml相对于properties更加精简而且很多官方给出的Demo都是yml的配置形式,在这里我们采用yml的形式代替properties,相 对于properties形式主要有以下两点不同
1 .对于键的描述由原有的"."分割变成了树的形状
2 .对于所有的键的后面一个要跟一个空格,不然启动项目会报配置解析错误
properties式语法描述
spring.datasource.name = mysql
spring.datasource.url = jdbc:mysql://localhost:3306/db?characterEncoding=utf-8
spring.datasource.username = root
spring.datasource.password = 123
yml式语法描述
spring:
datasource:
name: mysql
url: jdbc:mysql://localhost:3306/db?characterEncoding=utf-8
username: root
password: 123
配置所需依赖
快速启动项目创建成功后我们观察其pom.xml文件中的依赖如下图,包含了我们选择的Web、Mybatis以及Mysql
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.1
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-test
test
但是我们使用ORM框架一般还会配合数据库连接池以及分页插件来使用,在这里我选择了阿里的druid以及pagehelper这个分页 插件,再加上我们还需要整合swagger2文档自动化构建框架,所以增加了以下四个依赖项
com.github.pagehelper
pagehelper-spring-boot-starter
1.2.3
com.alibaba
druid-spring-boot-starter
1.1.1
com.alibaba
fastjson
1.2.31
io.springfox
springfox-swagger2
2.5.0
集成Mybatis
Mybatis的配置主要包括了druid数据库连接池、pagehelper分页插件、mybatis-generator代码逆向生成插件以及mapper、 pojo扫描配置
配置druid数据库连接池 添加以下配置至application.yml文件中
spring:
datasource:
如果存在多个数据源,监控的时候可以通过名字来区分开来 name: mysql
连接数据库的url
url: jdbc:mysql //[oca[host3306/db?characterEncoding=utf-8
连接数据库的账号
username: root
连接数据库的密码
password: 123
使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
扩展插件
监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
filters: stat
最大连接池数量
maxActive: 20
初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 initialsize: 1
获取连接时最大等待时间,单位毫秒
maxWait: 60000
最小连接池数量
minldle: 1
timeBetweenEvictionRunsMillis: 60000
连接保持空闲而不被驱逐的最长时间
minEvictableldleTimeMillis: 300000
用来检测连接是否有效的sql,要求是一个查询语句
如果validationQuery为null, testOnBorrow、testOnReturn、testWhileldle都不会其作用 validationQuery: select count(1) from ‘table’
申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 testWhileldle: true
申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 testOnBorrow: false
归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnReturn: false
是否缓存preparedStatement,即PSCache
poolPreparedStatements: false
要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true maxOpenPreparedStatements: -1
配置pagehelper分页插件
pagehelper分页插件
pagehelper:
#数据库的方言
helperDialect: mysql
#启用合理化,如果pageNum < 1会查询第一页,如果pageNum > pages会查询最后一页
reasonable: true
代码逆向生成插件mybatis-generator的配置及运行
mybatis-generator插件的使用主要分为以下三步
1 . pom.xml中添加mybatis-generator插件
org.springframework.boot
spring-boot-maven-p山gin
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.2
〈configuration)
${basedir}/src/main/resources/generator/generatorConfig.xml
true
true
〈/configuration)
2 .创建逆向代码生成配置文件generatorConfig.xml
参照pom.xml插件配置中的扫描位置,在resources目录下创建generator文件夹,在新建的文件夹中创建generatorConfig.xml 配置文件,文件的详细配置信息如下
优质文章,及时送达
作者 | smile_lg
链接 | blog.csdn.net/smile_lg/article/details/71215619
用来循环容器的标签forEach,查看例子
foreach元素的属性主要有item, index, collection, open, separator, close。
■ item:集合中元素迭代时的别名,
■ index:集合中元素迭代时的索引
■ open:常用语where语句中,表示以什么开始,比如以’(‘开始
■ separator:表示在每次进行迭代时的分隔符,
■ close常用语where语句中,表示以什么结束,
在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是 不一样的,主要有一下3种情况:
■ 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list.
■ 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array .
■ 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传 入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就 是传入的List或array对象在自己封装的map里面的key.
针对最后一条,我们来看一下官方说法:
注意你可以将一个List实例或者数组作为参数对象传给MyBatis,当你这么做的时候,MyBatis会自动将它包装在一个 Map中并以名称为键。List实例将会以“list”作为键,而数组实例的键将是“array”。
所以,不管是多参数还是单参数的list,array类型,都可以封装为map进行传递。如果传递的是一个List,则mybatis会封装为一 个list为key,list值为object的map,如果是array,则封装成一^个array为key, array的值为object的map,如果自己封装呢, 则colloection里放的是自己封装的map里的key值
〃mapperp我们要为这个方法传递的是一个容器将容器中的元素一个一个的
〃拼接到xml的方法中就要使用这个OrEach这个标签了
public List queryById(List userids);
〃对应的xml中如下
〈select id=“queryByld” resultMap=“BaseResEMap” >
select * FROM entity where id in
<foreach collection=“userids” item=“userid” index=“index” open=“(“separator=”,“close=”)”>
#{userid}
8慎,1模糊查询
〃比如说我们想要进行条件查询,但是几个条件不是每次都要使用,那么我们就可以
〃通过判断是否拼接到sql中
〈select id=“queryByld” resultMap=“BascResultMap” parameterType=“entity”>
SELECT * from entity
name like concat(’%‘,concat(#{name},’%‘))
choose (when, otherwise)标签
choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则choose结束。当choose中所有when 的条件都不满则时,则执行otherwise中的sql。类似于Java的switch语句,choose为switch, when为case, otherwise 则为 default。
例如下面例子,同样把所有可以限制的条件都写上,方面使用。choose会从上到下选择一个when标签的test为true的sql执行。 安全考虑,我们使用where将choose包起来,放置关键字多于错误。
! $0052判」断参数)-按顺序将实体类User第一个不为空的属性作为:where条件–>
〈select id=“getUserList_choose” resultMap=“resultMap_user” parameterType=“com.yiibai.pojo.User”> SELECT *
FROM User u
u.username LIKE CONCAT(CONCAT(’%‘, #{username, jdbcType=VARCHAR}),’%')
AND u.sex = #{sex, jdbcType=INTEGER}
AND u.birthday = #{birthday, jdbcType=DATE}
〈otherwise)
〈/otherwise)
selectKey 标签
在insert语句中,在Oracle经常使用序列、在MySQL中使用函数来自动生成插入表的主键,而且需要方法能返回这个生成主键。 使用myBatis的selectKey标签可以实现这个效果。
下面例子,使用mysql数据库自定义函数nextval(‘student’),用来生成一个key,并把他设置到传入的实体类中的studentId属 性上。所以在执行完此方法后,边可以通过这个实体类获取生成的key。
<–插入学生自动主键一〉
〈insert id=“createStudentAutoKey” parameterType=“liming.student.manager.data.model.StudentEntity” keyProperty=“studentId”>
select nextval(‘student’)
INSERT INTO STUDENT_TBL(STUDENT_ID,
STUDENT_NAME,
STUDENT_SEX,
STUDENT_BIRTHDAY,
STUDENT_PHOTO,
CLASS_ID,
PLACE_ID)
VALUES (#{studentId},
#{studentName},
#{studentSex},
#{studentBirthday},
#{studentPhoto, javaType=byte口, jdbcType=BLOB, typeHandler=org.apache.ibatis.type.BlobTypeHandler},
#{classId},
#{placeId})
调用接口方法,和获取自动生成key
StudentEntity entity = new StudentEntity();
entity.setStudentName(“黎明你好”);
entity.setStudentSex(l);
entity.setStudentBirthday(DateUtil.parse(“1985-05-28”));
entity.setClassId(“20000001”);
entity.setPlaceId(“70000001”);
this.dynamicSqlMapper.createStudentAutoKey(entity);
System.out.println("新增学生ID: " + entity.getStudentId());
if标签
if标签可用在许多类型的sql语句中,我们以查询为例。首先看一个很普通的查询:
喜欢文章,点个在看:::
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
腊月二十八,聊聊MyBatis中的设计模式
crazyant Java后端 1月22日
作者 | crazyant
链接 | www.crazyant.net/2022.html
虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅 读源码并观察设计模式在其中的应用,能够更深入的理解设计模式。
Mybatis至少遇到了以下的设计模式的使用:
1 . Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、
CacheBuilder;
2 .工厂模式,例如541565510口尸3壮0»、ObjectFactory、MapperProxyFactory;
3 .单例模式,例如ErrorContext和LogFactory;
4 .代理模式,Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使 用了cglib或者javassist达到延迟加载的效果;
5 .组合模式,例如SqlNode和各个子类ChooseSqlNode等;
6 .模板方法模式,例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;
7 .适配器模式,例如1。8的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
8 .装饰者模式,例如Cache包中的cache.decorators子包中等各个装饰者的实现;
9 .迭代器模式,例如迭代器模式PropertyTokenizer;
接下来挨个模式进行解读,先介绍模式自身的知识,然后解读在Mybatis中怎样应用了该模式。
1、8536「模式
Builder模式的定义是“将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。”,它属于创建类模 式,一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范围,就可以使用工厂模式和Builder模式,相对于工 厂模式会产出一个完整的产品,Builder应用于更加复杂的对象的构建,甚至只会构建产品的一个部分。
在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的MybatisMapConfig.xm^Mk 有的Mapper.xml文件,构建Mybatis运行的核心对象Configuration对象,然后将该Configuration对象作为参数构建一个 SqlSessionFactory对象。
其中乂乂1加口施8饰96『在构建Configuration对象时,也会调用XMLMapperBuilder用于读取Mapper文件,而 XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句。
在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法的解 析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此大量采用yBuilder模式来解决。
对于builder的具体类,方法都大都用build*开头,比如SqlSessionFactoryBuilder为例,它包含以下方法:
由 org.apache, ibatis.session
▼。口 > SqlSessionFactoryBuilder
• build(Configuration) : SqlSessionFactory
• build(lnputStream): Sq I Session Factory
• buildflnputStream, Properties): Sq I Sess ion Factory
• build(lnputStream, String): SqlSessionFactory
• build(lnputStream, String, Properties) : Sq I Sess ion Factory
• build(Reader) : SqlSessionFactory
• build(Reader, Properties): SqlSessionFactory
• buildfRcader, String): So SessionFactory
• buildfRcader, String, Properties) : SqlSessionFactory
即根据不同的输入参数来构建SqlSessionFactory这个工厂对象。
2、工厂模式
在Mybatis中比如SqlSessionFactory使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂模式。
简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工 厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常 都具有共同的父类。
SqlSession可以认为是一个Mybatis工作的核心的接口,通过这个接口可以执行执行SQL语句、获取Mappers、管理事务。类似 于连接MySQL的Connection对象。
田 org.apache.ibatis.session
▼ O j > SqlSessionFactory
• " getConfigurationO : Configuration
• " openSessionO : SqlSession
• " openSession(boolean): SqlSession
• A openSession(Connection): SqlSession
• A openSession(ExecutorType) : SqlSession
• A openSession(ExecutorType, boolean) : SqlSession
• ’ openSession(ExecutorType, Connection) : SqISess on
• 4 openSession(ExecutorType, TransactionlsolationLevel) : SqlSession
• " open Session (Transaction I so lat ion Leve I): SqlSession
可以看到,该Factory的openSession方法重载了很多个,分别支持autoCommit、Executor、Transaction等参数的输入,来 构建核心的SqlSession对象。
在DefaultSqlSessionFactory的默认工厂实现里,有一个方法可以看出工厂怎么产出一个产品:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); //may ha ve fetched a connection so lets cal
// cose(
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这是一个openSession调用的底层方法,该方法先从configuration读取对应的环境配置,然后初始化TransactionFactory获得 一个Transaction对象,然后通过Transaction获取一个Executor对象,最后通过configuration、Executor、是否autoCommit 三个参数构建ySqlSession。
在这里其实也可以看到端倪,SqlSession的执行,其实是委托给对应的Executor来进行的。
而对于LogFactory,它的实现代码:
public final class LogFactory {
private static Constructor<? extends Log> logConstructor; private LogFactory() { // disable construction } public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
这里有个特别的地方,是1。8变量的的类型是Constructor<?extendsLog》,也就是说该工厂生产的不只是一个产品,而是具有 Log公共接口的一系列产品,比如Log4jImpl、Slf4jImpl等很多具体的Log。
3、单例模式
单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为 单例类,它提供全局访问的方法。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实 例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。
class Singleton ) instance
A
Singleton
- instance :Singleton
if (instance == NULL)
instance = new SingletonQ; return instance;
I , + getlnstanceO :Singleton
- singletonOperationO rvoid « constructor®
- SingletonQ :void
在Mybatis中有两个地方用到单例模式,ErrorContext和LogFactory, 其中ErrorContext是用在每个线程范围内的单例,用于记 录该线程的执行环境错误信息,而LogFactory则是提供给整个Mybatis使用的日志工厂,用于获得针对项目配置好的日志对象。
ErrorContext的单例实现代码:
public class ErrorContext {
private static final ThreadLocal LOCAL = new ThreadLocal();
private ErrorContext () {
}
public static ErrorContext instance () {
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext。;
LOCAL.set(context);
}
return context;
}
构造函数是private修饰,具有一个static的局部instance变量和一个获取instance变量的方法,在获取实例的方法中,先判断是 否为空如果是的话就先创建,然后返回构造好的对象。
只是这里有个有趣的地方是,LOCAL的静态实例变量使用了ThreadLocal修饰,也就是说它属于每个线程各自的数据,而在 instance。方法中,先获取本线程的该实例,如果没有就创建该线程独有的ErrorContext。
4、代理模式
代理模式可以认为是Mybatis的核心使用的模式,正是由于这个模式,我们只需要编写Mapper.java接口,不需要实现,由 Mybatis后台帮我们完成具体SQL的执行。
代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或 Surrogate,它是一种对象结构型模式。
代理模式包含如下角色:
• Subject:抽象主题角色
• Proxy:代理主题角色
• RealSubject:真实主题角色
这里有两个步骤,第一个是提前创建一个Proxy,第二个是使用的时候会自动请求Proxy,然后由Proxy来执行具体事务;
当我们使用Configuration的getMapper方法时,会调用mapperRegistry.getMapper方法,而该方法又会调用 mapperProxyFactory.newInstance(sqlSession)来生成一^个具体的代理:
*
@author Lasse Voss /
public class MapperProxyFactory {
private final Class mapperinterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class mapperinterface) {
this.mapperinterface = mapperinterface;
}
public Class getMapperInterface() { return mapperinterface;
}
public Map<Method, MapperMethod> getMethodCache () { return methodCache;
}
@SuppressWarnings(" unchecked")
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyinstance(mapperinterface.getClassLoader(), new Class[] { mapperinterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperinterface, methodCache);
return newinstance(mapperProxy);
}
}
在这里,先通过T newinstance(SqlSession sqlSession)方法会得到一^个MapperProxy对象,然后调用T
newinstance(MapperProxy mapperProxy)生成代理对象然后返回。
而查看MapperProxy的代码,可以看到如下内容:
public class MapperProxy implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object口 args) throws Throwable { try {
if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
非常典型的,该MapperProxy类实现yinvocationHandler接口,并且实现了该接口的invoke方法。
通过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个Mapper接口的时候,就会转发给MapperProxy.invoke方 法,而该方法则会调用后续的sqlSession.cud>executor.execute>prepareStatement等一系列方法,完成SQL的执行和返回。
5、组合模式 组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。
组合模式对单个对象(叶子对象)和组合对象(组合对象)具有一致性,它将对象组织到树结构中,可以用来描述整体与部分的关系。 同时它也模糊了简单元素(叶子对象)和复杂元素(容器对象)的概念,使得客户能够像处理简单元素一样来处理复杂元素,从而使客 户程序能够与复杂元素的内部结构解耦。
在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子 节点和对象节点进行一致处理的原因。
Mybatis支持动态SQL的强大功能,比如下面的这个SQL:
〈update id=“update” parameterType=“org.format.dynamicproxy.mybatis.bean.User”>
UPDATE users
〈trim prefix=“SET” prefixOverrides=“,”>
name = #{name}
,age = {{age}
,birthday = #{bithday}
where id = ${id}
在这里面使用到了"1巾、[£等动态元素,可以根据条件来生成不同情况下的SQL; 在DynamicSqlSource.getBoundSql方法里,调用yrootSqlNode.apply(context)方法,apply方法是所有的动态节点都实现 的接口:
public interface SqINode {
boolean apply(DynamicContext context);
}
对于实现该SqlSource接口的所有节点,就是整个组合模式树的各个节点:
Type hierarchy of ‘org.apache.ibatis.scripting.xmhags.SqlNode1: ▼ O SqINode - org.apache.ibatis.scripting.xmltags
ChooseSqINode - org.apacheJbatis.scripting.xmltags ForEachSqiNode - org.apache, i bat is .script ing. xm Itags IfSqINode ・ org.apache.ibatis.scripting.xmltags MixedSqINode - org.apache.ibatis.scripting.xmltags StaticTextSq’ Node org.apache.ibatis.scripting.xmltags TextSqINode org.apache. oatis scripting.xmltags TrirnSqlNode - org.apache.ibatis.scripting.xmltags
0 SetSqINode org apache.ibat s scripting.xm tags
O WhereSqINode - org.apache.ibatis.scripting.xmltags O VarDecISqINodc org apache.ibatis.scripting.xmltags
Press XT to see the supertype hierarchy
组合模式的简单之处在于,所有的子节点都是同一类节点,可以递归的向下执行,比如对于TextSqINode,因为它是最底层的叶 子节点,所以直接将对应的内容append到SQL语句中:
@Override
public boolean apply(DynamicContext context) {
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
context.appendSql(parser.parse(text));
return true;
}
但是对于IfSqlNode,就需要先做判断,如果判断通过,仍然会调用子元素的SqINode,即contents.apply方法,实现递归的解 析。
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
6、模板方法模式
模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术。
模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负
责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本方法汇总起来 的方法叫做模板方法(template method),这个设计模式的名字就是从此而来。
模板类定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某 些特定步骤。
在乂丫53也中,sqlSession的SQL执行,都是委托给Executor实现的,Executor包含以下结构:
其中的BaseExecutor就采用了模板方法模式,它实现了大部分的SQL执行逻辑,然后把以下几个方法交给子类定制化完成:
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
protected abstract List doFlushStatements (boolean isRollback) throws SQLException;
protected abstract List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
该模板方法类有几个子类的具体实现,使用了不同的策略:
• 简单SimpleExecutor:每执行一次update或select,就开启一^个Statement对象,用完立刻关闭Statement对象。(可以 是Statement或PrepareStatement对象)
• 重用ReuseExecutor:执行update或select,以sql作为卜6丫查找51316巾6口1对象,存在就使用,不存在就创建,用完后,
不关闭Statement对象,而是放置于Map<String, Statement〉内,供下一次使用。(可以是Statement或
PrepareStatement对象)
• 批量BatchExecutor:执行update (没有select, JDBC批处理不支持select),将所有sql都添加到批处理中 (addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是
addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多 属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或 PrepareStatement对象)
比如在SimpleExecutor中这样实现update方法:
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null,
null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
7、适配器模式
适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作, 其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
在Mybatsi的logging包中,有一个Log接口:
*
@author Clinton Begin
/
public interface Log {
boolean isDebugEnabled ();
boolean isTraceEnabled();
void error(String s, Throwable e);
void error(String s);
void debug(String s);
void trace(String s);
void warn(String s);
}
该接口定义了Mybatis直接使用的日志方法,而Log接口具体由谁来实现呢? Mybatis提供了多种日志框架的实现,这些实现都匹 配这个Log接口所定义的接口方法,最终实现了所有外部曰志框架到Mybatis曰志包的适配:
:Type hierarchy of 'org.apache.ibatis.logging.Log1:
JakartaCommonsLogg:nglmpl - org.apacr s. logg i ng .com mon s
Jdk14Logginglmpl - org.apacheJbatis.loggingjdk14
Log4>2AbstractLoggcrlmpl - org.apache.ibatis.logging.Iog4j2
Log4j2lmpl - org.apache.ibatis.logging. og4j2
Log4j2Loggerlmpl - org.apacheJbatisJoggingJog4j2
Log4jlmpl - org.apache.ibatisJogging.Iog4j
NoLogginglmpI - org.apache.ibatis.logging.nologging
Slf4jl(npl - org.apache.ibatis.logging.slf4j
Slf4jLocationAwareLoggerlmpl - org.apacheJbatis.logging.slf4j
Slf4jLoggerlmpl - org.apache.ibatis.I ogging.slf4j
StdOutlmpI - org.apache.ibatis. logging.stdout
比如对于Log4jImpl的实现来说,该实现持有了org.apache.log4j.Logger的实例,然后所有的日志方法,均委托该实例来实现。
public class Log4jImpl implements Log {
private static final String FQCN = Log4jImpl.class.getName();
private Logger log;
public Log4jImpl(String clazz) { log = Logger.getLogger(clazz);
}
@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}
@Override
public boolean isTraceEnabled () {
return log.isTraceEnabled();
}
@Override
public void error(String s, Throwable e) { log.log(FQCN, Level.ERROR, s, e);
}
@Override
public void error(String s) { log.log(FQCN, Level.ERROR, s, null);
}
@Override
public void debug(String s) { log.log(FQCN, Level.DEBUG, s, null);
}
@Override
public void trace(String s) { log.log(FQCN, Level.TRACE, s, null);
}
@Override
public void warn (String s) { log.log(FQCN, Level.WARN, s, null);
}
}
8、装饰者模式 装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生 成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的 不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。
在mybatis中,缓存的功能由根接口Cache (org.apache.ibatis.cache.Cache)定义。整个体系采用装饰器设计模式,数据存储 和缓存的基本功能由PerpetualCache (org.apache.ibatis.cache.impl.PerpetualCache)永久缓存实现,然后通过一系列的装 饰器来对PerpetualCache永久缓存进行缓存策略等方便的控制。如下图:
用于装饰PerpetualCache的标准装饰器共有8个(全部在org.apache.ibatis.cache.decorators包中):
- FifoCache:先进先出算法,缓存回收策略
- LoggingCache:输出缓存命中的日志信息
- LruCache:最近最少使用算法,缓存回收策略
- ScheduledCache:调度缓存,负责定时清空缓存
- SerializedCache:缓存序列化和反序列化存储
- SoftCache:基于软引用实现的缓存管理策略
- SynchronizedCache:同步的缓存装饰器,用于防止多线程并发访问
- WeakCache:基于弱引用实现的缓存管理策略
另外,还有一个特殊的装饰器TransactionalCache:事务性的缓存
正如大多数持久层框架一样,mybatis缓存同样分为一级缓存和二级缓存
• 一级缓存,又叫本地缓存,是PerpetualCache类型的永久缓存,保存在执行器中(BaseExecutor),而执行器又在 SqlSession (DefaultSqlSession)中,所以一级缓存的生命周期与SqlSession是相同的。
• 二级缓存,又叫自定义缓存,实现了Cache接口的类都可以作为二级缓存,所以可配置如encache等的第三方缓存。二级缓 存以namespace名称空间为其唯一标识,被保存在Configuration核心配置对象中。
二级缓存对象的默认类型为PerpetualCache,如果配置的缓存是默认类型,则mybatis会根据配置自动追加一系列装饰器。
Cache对象之间的引用顺序为:
SynchronizedCache->LoggingCache->SerializedCache->ScheduledCache->LruCache->PerpetualCache
9、迭代器模式
迭代器(Iterator)模式,又叫做游标(Cursor)模式。GOF给出的定义为:提供一种方法访问一个容器(container)对象中各 个元素,而又不需暴露该对象的内部细节。
Java的Iterator就是迭代器模式的接口,只要实现了该接口,就相当于应用了迭代器模式:
• O lterator
• 0 forEachRemaining(Consumer<? super E>): void
• A hasNextO : boolean
• A next() : E
• 口 removed : void
比如Mybatis的PropertyTokenizer是property包中的重量级类,该类会被reflection包中其他的类频繁的引用到。这个类实现了 Iterator接口,在使用时经常被用到的是Iterator接口中的hasNext这个函数。
public class PropertyTokenizer implements Iterator {
private String name;
private String indexedName;
private String index;
private String children;
public PropertyTokenizer (String fullname) {
int delim = fullname.indexOf(‘.’);
if (delim > -1) {
name = fullname.substring(0, delim);
children = fullname.substring(delim + 1);
} else {
name = fullname;
children = null;
}
indexedName = name;
delim = name.indexOf(‘[’);
if (delim > -1) {
index = name.substring(delim + 1, name.length() - 1);
name = name.substring(0, delim);
}
}
public String getName() { return name;
}
public String getIndex () { return index;
}
public String getIndexedName() {
return indexedName;
}
public String getChildren() {
return children;
}
@Override
public boolean hasNext() {
return children != null;
}
@Override
public PropertyTokenizer next() {
return new PropertyTokenizer(children);
}
@Override
public void remove () {
throw new UnsupportedOperationException(
“Remove is not supported, as it has no meaning in the context of properties.”);
}
}
可以看到,这个类传入一个字符串到构造函数,然后提供了iterator方法对解析后的子串进行遍历,是一个很常用的方法类。
参考资料:
•图说设计模式:http://design-patterns.readthedocs.io/zh_CN/latest/index.html
•深入浅出Mybatis系列(十)一SQL执行流程分析(源码篇):http://www.cnblogs.eom/dongying/p/4142476.html
•设计模式读书笔记一-组合模式http://www.cnblogs.eom/chenssy/p/3299719.html
• Mybatis3.3.x技术内幕(四):五鼠闹东京之执行器£乂6切1。『设计原
本http://blog.csdn.net/wagcy/article/details/32963235
• mybatis缓存机制详解(一) Cache https://my.oschina.net/lixin91/blog/620068
本文地址:http://crazyant.net/2022.html,转载请注明来源。
微信扫描二维码,关注我的公众号
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!
面试官:讲一下Mybatis初始化原理
亦山 Java后端 2019-11-23
优质文章,及时送达
作者|亦山
原文 | https://urlify.cn/zaYRJv
对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外。本章将通过以下几点详细介绍MyBatis的初始 化过程。
1 . MyBatis的初始化做了什么
2 . MyBatis基于XML配置文件创建Configuration对象的过程
3 .手动加载XML配置文件创建Configuration对象完成初始化,创建并使用SqlSessionFactory对象
4 .涉及到的设计模式
一、乂丫8,也$的初始化做了什么
任何框架的初始化,无非是加载自己运行时所需要的配置信息。MyBatis的配置信息,大概包含以下信息,其高层级结构如 下:
x configuration 配置
X properties 属性
x settings 设置
X typeAliases类型命名
X typeHandlers类型处理器
X objectFactory 对象工厂
x plugins 插件
X environments 壬环境
X environment环境变量
X transactionManager 事务管理器
X dataSource 数据源
乂映射器
MyBatis的上述配置信息会配置在XML配置文件中,那么,这些信息被加载进入MyBatis内部,MyBatis是怎样维护的呢?
MyBatis采用了一个非常直白和简单的方式—使用org.apache.ibatis.session.Configuration对象作为一个所有配置 信息的容器,Configuration对象的组织结构和XML配置文件的组织结构几乎完全一样(当然,Configuration对象的功能 并不限于此,它还负责创建一些MyBatis内部使用的对象,如Executor等,这将在后续的文章中讨论)。如下图所示:
MyBatis根据初始化好Configuration信息,这时候用户就可以使用MyBatis进行数据库操作了。
可以这么说,MyBatis初始化的过程,就是创建Configuration对象的过程。
MyBatis的初始化可以有两种方式:
。基于XML配置文件:基于XML配置文件的方式是将MyBatis的所有配置信息放在XML文件中,MyBatis通过加载并 XML配置文件,将配置文信息组装成内部的Configuration对象
。基于Java API:这种方式不使用XML配置文件,需要MyBatis使用者在Java代码中,手动创建Configuration对象, 然后将配置参数set进入Configuration对象中
(PS: MyBatis具体配置信息有哪些,又分别表示什么意思,不在本文的叙述范围,读者可以参考我的《Java Persistence
withMyBatis 3 (中文版)》的第二章引导MyBatis中有详细的描述)
接下来我们将通过基于XML配置文件方式的MyBatis初始化,深入探讨MyBatis是如何通过配置文件构建Configuration 对象,并使用它的。
二、MyBatis基于XML配置文件创建Configuration对象的过程
现在就从使用MyBatis的简单例子入手,深入分析一下MyBatis是怎样完成初始化的,都初始化了什么。看以下代码:
String resource = “mybatis-config.xml”;
Inputstream inputstream = Resources.getResourceAsStream(resource);
SqlSessionFactory SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputs tream);
SqlSession sqlSession = SqlSessionFactory.openSession();
List list = sqlSession.selectList(“com.foo.bean.BlogMapper.queryAHBloglnfo”);
有过MyBatis使用经验的读者会知道,上述语句的作用是执行com.foo.bean.BlogMapper.queryAllBlogInfo 定义的 SQL语句,返回一个List结果集。总的来说,上述代码经历了巾丫53^5初始化–>创建SqlSession -->执行SQL语句返回结 果三个过程。
上述代码的功能是根据配置文件mybatis-config.xml配置文件,创建SqlSessionFactory对象,然后产生SqlSession, 执行SQL语句。而mybatis的初始化就发生在第三句:SqlSessionFactory SqlSessionFactory = new
SqlSessionFactoryBuilder().build(inputStream);现在就让我们看看第三句到底发生了什么。
乂丫83籍5初始化基本过程:
SqlSessionFactoryBuilder根据传入的数据流生成Configuration对象,然后根据Configuration对象创建默认的 SqlSessionFactory实例。
初始化的基本过程如下序列图所示:
5 . buildfconfiguration)返回
new DefaultSqlSessionFactory(config)
由上图所示,mybatis初始化要经过简单的以下几步:
- 调用SqlSessionFactoryBuilder对象的build(inputStream)方法;
- SqlSessionFactoryBuilder会根据输入流inputStream等信息创建XMLConfigBuilder对象;
- SqlSessionFactoryBuilder调用XMLConfigBuilder对象的parse()方法;
- XMLConfigBuilder对象返回Configuration对象;
- SqlSessionFactoryBuilder根据Configuration对象创建一^个DefaultSessionFactory对象;
- SqlSessionFactoryBuilder返回 DefaultSessionFactory对象给Client,供Client使用。
SqlSessionFactoryBuilder相关的代码如下所示:
public SqlSessionFactory build(Inputstream inputstream)
return build(inputstream, null, null);
public SqlSessionFactory build(Inputstream inputStream, String environment. Properties properties)
try
(
//2.创建XMLConfigBuilder对象用来解析XML配置文件,生成Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStreanij environment, properties);
//3.将XML配置文件内的信息解析成Java对象Configuration对象
Configuration config = parser.parse();
〃4.根据 Configuration 对象创建出 SqlSessionFactory 对象
return build(config);
catch (Exception e)
(
throw ExceptionFactory.wrapException(*'Error building SqlSession. e)
finally (
上述的初始化过程中,涉及到了以下几个对象:
S SqlSessionFactoryBuilder :SqlSessionFactory的构造器,用于创建SqlSessionFactory,采用yBuilder设计 模式
。Configuration :该对象是mybatis-config.xml文件中所有mybatis配置信息
。SqlSessionFactory:SqlSession工厂类,以工厂形式创建SqlSession对象,采用yFactory工厂设计模式
。XmlConfigParser :负责将mybatis-config.xml配置文件解析成Configuration对象,共
SqlSessonFactoryBuilder使用,仓I」建SqlSessionFactory
创建Configuration对象的过程
接着上述的MyBatis初始化基本过程讨论,当541565510口尸3前0»85旧6『执行build()方法,调用yXMLConfiigBuilder 的parse()方法,然后返回了口口甘目④山日。1对象。那么parse()方法是如何处理XML文件,生成Configuration对象的呢?
1 . XMLConfigBuilder会将XML配置文件的信息转换为Document对象,而XML配置定义文件DTD转换成
XMLMapperEntityResolver对象,然后将二者封装到XpathParser对象中,XpathParser的作用是提供根据Xpath表达 式获取基本的DOM节点N。de信息的操作。如下图所示:
2 .之后XMLConfigBuilder调用parse()方法:会从XPathParser中取出<configuration>节点对应的Node对象,然后 解析此Node节点的子Node:properties, settings, typeAliases,typeHandlers, objectFactory,
objectWrapperFactory, plugins, environments,databaseIdProvider, mappers
public Configuration parse()
(
if (parsed)
(
throw new BuilderException(“Each XMLConfigBuilder can only be used once.0);
)
parsed = true;
〃源码中没有这一句,只有
parseConfiguration(parser.evalNode(”/conf iguration**));
〃为了让读者看得更明麻,源码拆分为以下两句
XNode conf igurationNode = parser. evalNode(r’/configur at ion");
parseConfiguration(configurationNode);
return configuration;
)
/*
解析"/configuration”节点下的子节点信息,然后将解析的结果设置到Configuration对象中
*/
private void parseConfiguration(XNode root) {
try {
〃1 .苜先处理properties节点
propertiesElement(root.evalNode(MpropertiesM)); //issue #117 read properties first
〃2 .处理 typeAliases
typeAliasesElement(root.evalNode(“typeAliases”));
〃3.处理插件
pluginElement(root.evalNode(“plugins”));
〃4.处理 object Factory
objectFactoryElement(root.evalNode(“objectFactory”));
//5.objectWrapperFactory
objectHrapperFactoryElement(root .evalNodeCobjectWrapperFactory"));
//6.settings
settingsElement(root.eva!Node( “settings **));
〃7 .处理 environments
environmentsElement(root.evalNode(“environmentsM)); // read it after objectFactory and objectWrapperFactory issue #631
//8.database
databaseIdProviderElement(root.evalNodeC’databaseldProvider”));
//9. typeHandlers
typeHandlerElement(root.evalNode(“typeHandlers”));
//10 mappers
mapperElement(root.evalNodef"mappers”));
} catch (Exception e) {
throw new BuilderException(“Error parsing SQL Mapper Configuration. Cause: + e, e);
注意:在上述代码中,还有一个非常重要的地方,就是解析XML配置文件子节点<mappers》的方法 mapperElements(root.evalNode(“mappers”)),它将解析我们配置的Mapper.xml配置文件,Mapper配置文件可以 说是MyBatis的核心,MyBatis的特性和理念都体现在此Mapper的配置和设计上,我们将在后续的文章中讨论它,敬请期 待〜
3 .然后将这些值解析出来设置到Configuration对象中。
解析子节点的过程这里就不一一介绍了,用户可以参照MyBatis源码仔细揣摩,我们就看上述的 environmentsElement(root.evalNode(“environments”));方法是如何将environments的信息解析出来,设置到 Configuration对象中的:
解析environments节点,并将结果设置到Configuration对象中
注意:创建envronment时,如果SqlSessionFactopyBuilder指定了特定的环境(即数据源); 则返回指定环境(数据源)的Environment对象,否则返回默认的Environment对象; 这种方式实现了 MyBatis可以连接多数据源
*/
private void environmentsElement(XNode context) throws Exception
{
if (context != null)
(
if (environment == null)
(
environment = context.getStringAttribute(“default”);
)
for (XNode child : context.getChildren()) (
String id = child.getStringAttribute(“id”);
if (isSpecifiedEnvironB>ent(id))
(
〃▲.创建事务工厂 TransactionFactory
TransactionFactory txFactory =
transactionManagerElement(child.evalNode(“transactionManager”)); DataSourceFactory dsFactory =
dataSourceElement (child. evalNode( ^dataSource1’));
//2.创建数据源Dat aSource
DataSource dataSource = dsFactory.getDataSource();
//3.构造Environment对象
Environment.Builder environmentBuilder = new
Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//4.将创建的Envronment对象设置到configuration对象中
configuration.setEnvironment(environmentBuilder.build()); }
private boolean isSpecifiedEnvironment(String id) {
if (environment == null) (
throw new BuilderException(MNo environment specified.”);
}
else if (id == null) (
throw new BuilderException(MEnvironment requires an id attribute.0); } else if (environment.equals(id)) (
return true;
}
return false;
4 .返回Configuration对象
我们将上述的MyBatis初始化基本过程的序列图细化。
三、手动加载XML配置文件创建Configuration对象完成初始化,创建并使用SqlSessionFactory对象
我们可以使用XMLConfigBuilder手动解析XML配置文件来创建Configuration对象,代码如下:
String resource = "mybatis-config-xml1’;
Inputstream inputStream = Resources.getResourceAsStream(resource);
〃手动创建XMLConfigBuilder,并解析创建Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStreaiDj null,null);
Configuration configuration=parse();
〃使用 Conf igu ration 对象创建 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(config □ration);
四、涉及到的设计模式
初始化的过程涉及到创建各种对象,所以会使用一些创建型的设计模式。在初始化的过程中,Builder模式运用的比较多。
Builder模式应用1:SqlSessionFactory的创建
(Reader)
(Reader, String)
(Reader? Propertiesj
(Reader, Stringy Properties)
(InputS tr earn)
(Inputstre孙 String)
(Input士tream, Properties) ttuputStream, String, Properties)
'.Lonfi gur ati on)
Designed by LouLuan
http://blog-cadn.net/luanlouis
由于构造时参数不定,可以为其创建一个构造器Builder,将SqlSessionFactory的构建过程和表示分开:
C SqlSessi onFactoryBuilder
T build (Header) S^SessionFactory
ni build (Reader, String) SqlSessi onFact ory
m build(R«td«r, Proptrti<s) SqlStssionFtdory
m build<B*«dtrr String, Proptrtits) SqlStssionFactory
m build Q.nput Str earn) SqlSessionFactory
m build^TnputStrearn. String) SqlSessionFactory
m builddi9utStream, Properti es) SqlSessionFactory
m build(Jiq>utStreain/ String Properties) SqlSessionFactory
m build (Configuration) SqlSessi onFactory tcreatej
1 SqlSessionFactory
Designed by LouLuan http://blog.csdn.net/luanlouis
MyBatis将SqlSessionFactoryBuilder和SqlSessionFactory相互独立。
Builder模式应用2:数据库连接环境Environment对象的创建 在构建Configuration对象的过程中,XMLConfiigParser解析 mybatis XML配置文件节点<6"“什0"巾6口6节点时,会有 以下相应的代码:
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) { environment = context.getStringAttribute(“default”);
}
for (XNode child : context.getChildren()) { String id = child.getStringAttribute(Mid”); 〃是和默认的环境相同时,解析之 if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory =
transactionManagerElement(child.evalNode(“transactionManager”));
DataSourceFactory dsFactory =
dataSourceElementCchild.evalNodeCdataSource"));
DataSource dataSource = dsFactory.getDataSource();
〃使用了Environment内置的构造器Builder,传递id事务工厂和数据源
Environment.Builder environmentBuilder = new Environment.Builder(id) ,transactionFactory(txFactory) .dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
在Environment内部,定义了静态内部Builder类:
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
if (id == null) {
throw new IllegalArgumentException(“Parameter ‘id’ must not be null”);
}
if (transactionFactory == null) {
throw new IllegalArgumentException( ,Parameter ‘transactionFactory’ must n ot be null");
}
this.id = id;
if (dataSource == null) {
throw new IllegalArgumentException("Parameter 'dataSource* must not be null
}
this.transactionFactory = transactionFactory;
this.dataSource = dataSource;
}
public static class Builder {
private String id;
private TransactionFactory transactionFactory;
private DataSource dataSource;
public Builder(String id) {
以上就是本文《深入理解mybatis原理》Mybatis初始化机制详解的全部内容,希望对大家有所帮助!上述内容如有不妥之 处,还请读者指出,共同探讨,共同进步!
【END】
推荐阅读
1我采访了一位Pornhub工程师,聊了这些纯纯的话题
2 .常见排序算法总结-Java实现
3 .Java:如何更优雅的处理空值?
4 . MySQL:数据库优化,可以看看这篇文章
5 .团队开发中Git最佳实践
喜欢文章,点个在看**J
声明:pdf仅供学习使用,一切版权归原创公众号所有;建议持续关注原创公众号获取最新文章,学习愉快!