Mybatis系列第4篇:Mybatis使用详解(2),一起跟上节奏!

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

18:51.271 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <==      Total: 4

18:51.272 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)

18:51.274 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)

18:51.274 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)

18:51.274 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)

输出正常。

别名不区分大小写

我们可以将上面UserMapper.xml中的use别名改成大写的:USER,如下:

<![CDATA[ SELECT * FROM t_user ]]>

然后再运行一下com.javacode2018.chat03.demo1.UserMapperTest#getUserList,如下:

42:49.474 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==>  Preparing: SELECT * FROM t_user

42:49.509 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:

42:49.527 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <==      Total: 4

42:49.528 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)

42:49.530 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)

42:49.530 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)

42:49.531 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)

也是正常的,说明别名使用时是不区分大小写的。

mybatis内置的别名

mybatis默认为很多类型提供了别名,如下:

| 别名 | 对应的实际类型 |

| — | — |

| _byte | byte |

| _long | long |

| _short | short |

| _int | int |

| _integer | int |

| _double | double |

| _float | float |

| _boolean | boolean |

| string | String |

| byte | Byte |

| long | Long |

| short | Short |

| int | Integer |

| integer | Integer |

| double | Double |

| float | Float |

| boolean | Boolean |

| date | Date |

| decimal | BigDecimal |

| bigdecimal | BigDecimal |

| object | Object |

| map | Map |

| hashmap | HashMap |

| list | List |

| arraylist | ArrayList |

| collection | Collection |

| iterator | Iterator |

上面这些默认都是在org.apache.ibatis.type.TypeAliasRegistry类中进行注册的,这个类就是mybatis注册别名使用的,别名和具体的类型关联是放在这个类的一个map属性(typeAliases)中,贴一部分代码大家感受一下:

public class TypeAliasRegistry {

private final Map<String, Class<?>> typeAliases = new HashMap<>();

public TypeAliasRegistry() {

registerAlias(“string”, String.class);

registerAlias(“byte”, Byte.class);

registerAlias(“long”, Long.class);

registerAlias(“short”, Short.class);

registerAlias(“int”, Integer.class);

registerAlias(“integer”, Integer.class);

registerAlias(“double”, Double.class);

registerAlias(“float”, Float.class);

registerAlias(“boolean”, Boolean.class);

registerAlias(“byte[]”, Byte[].class);

registerAlias(“long[]”, Long[].class);

registerAlias(“short[]”, Short[].class);

registerAlias(“int[]”, Integer[].class);

registerAlias(“integer[]”, Integer[].class);

registerAlias(“double[]”, Double[].class);

registerAlias(“float[]”, Float[].class);

registerAlias(“boolean[]”, Boolean[].class);

registerAlias(“_byte”, byte.class);

registerAlias(“_long”, long.class);

registerAlias(“_short”, short.class);

registerAlias(“_int”, int.class);

registerAlias(“_integer”, int.class);

registerAlias(“_double”, double.class);

registerAlias(“_float”, float.class);

registerAlias(“_boolean”, boolean.class);

registerAlias(“_byte[]”, byte[].class);

registerAlias(“_long[]”, long[].class);

registerAlias(“_short[]”, short[].class);

registerAlias(“_int[]”, int[].class);

registerAlias(“_integer[]”, int[].class);

registerAlias(“_double[]”, double[].class);

registerAlias(“_float[]”, float[].class);

registerAlias(“_boolean[]”, boolean[].class);

registerAlias(“date”, Date.class);

registerAlias(“decimal”, BigDecimal.class);

registerAlias(“bigdecimal”, BigDecimal.class);

registerAlias(“biginteger”, BigInteger.class);

registerAlias(“object”, Object.class);

registerAlias(“date[]”, Date[].class);

registerAlias(“decimal[]”, BigDecimal[].class);

registerAlias(“bigdecimal[]”, BigDecimal[].class);

registerAlias(“biginteger[]”, BigInteger[].class);

registerAlias(“object[]”, Object[].class);

registerAlias(“map”, Map.class);

registerAlias(“hashmap”, HashMap.class);

registerAlias(“list”, List.class);

registerAlias(“arraylist”, ArrayList.class);

registerAlias(“collection”, Collection.class);

registerAlias(“iterator”, Iterator.class);

registerAlias(“ResultSet”, ResultSet.class);

}

}

mybatis启动的时候会加载全局配置文件,会将其转换为一个org.apache.ibatis.session.Configuration对象,存储在内存中,Configuration类中也注册了一些别名,代码如下:

typeAliasRegistry.registerAlias(“JDBC”, JdbcTransactionFactory.class);

typeAliasRegistry.registerAlias(“MANAGED”, ManagedTransactionFactory.class);

typeAliasRegistry.registerAlias(“JNDI”, JndiDataSourceFactory.class);

typeAliasRegistry.registerAlias(“POOLED”, PooledDataSourceFactory.class);

typeAliasRegistry.registerAlias(“UNPOOLED”, UnpooledDataSourceFactory.class);

typeAliasRegistry.registerAlias(“PERPETUAL”, PerpetualCache.class);

typeAliasRegistry.registerAlias(“FIFO”, FifoCache.class);

typeAliasRegistry.registerAlias(“LRU”, LruCache.class);

typeAliasRegistry.registerAlias(“SOFT”, SoftCache.class);

typeAliasRegistry.registerAlias(“WEAK”, WeakCache.class);

typeAliasRegistry.registerAlias(“DB_VENDOR”, VendorDatabaseIdProvider.class);

typeAliasRegistry.registerAlias(“XML”, XMLLanguageDriver.class);

typeAliasRegistry.registerAlias(“RAW”, RawLanguageDriver.class);

typeAliasRegistry.registerAlias(“SLF4J”, Slf4jImpl.class);

typeAliasRegistry.registerAlias(“COMMONS_LOGGING”, JakartaCommonsLoggingImpl.class);

typeAliasRegistry.registerAlias(“LOG4J”, Log4jImpl.class);

typeAliasRegistry.registerAlias(“LOG4J2”, Log4j2Impl.class);

typeAliasRegistry.registerAlias(“JDK_LOGGING”, Jdk14LoggingImpl.class);

typeAliasRegistry.registerAlias(“STDOUT_LOGGING”, StdOutImpl.class);

typeAliasRegistry.registerAlias(“NO_LOGGING”, NoLoggingImpl.class);

typeAliasRegistry.registerAlias(“CGLIB”, CglibProxyFactory.class);

typeAliasRegistry.registerAlias(“JAVASSIST”, JavassistProxyFactory.class);

上面有2行如下:

typeAliasRegistry.registerAlias(“JDBC”, JdbcTransactionFactory.class);

typeAliasRegistry.registerAlias(“POOLED”, PooledDataSourceFactory.class);

上面这2行,注册了2个别名,别名和类型映射关系如下:

JDBC -> JdbcTransactionFactory

POOLED -> PooledDataSourceFactory

上面这2个对象,大家应该比较熟悉吧,mybatis全局配置文件(chat03\src\main\resources\demo1\mybatis-config.xml)中我们用到过,我们再去看一下,如下:

上面2个红框的是不是就是上面注册的2个类型,上面xml中我们写的是完整类型名称,我们可以将其改为别名的方式也是可以的,如下:

我们来运行com.javacode2018.chat03.demo1.UserMapperTest#getUserList,看一下能否正常运行,输出如下:

44:10.886 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==>  Preparing: SELECT * FROM t_user

44:10.929 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:

44:10.947 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <==      Total: 4

44:10.948 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)

44:10.950 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)

44:10.950 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)

44:10.950 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)

很好,一切正常的。

别名的原理

mybatis允许我们给某种类型注册一个别名,别名和类型之间会建立映射关系,这个映射关系存储在一个map对象中,key为别名的名称,value为具体的类型,当我们通过一个名称访问某种类型的时候,mybatis根据类型的名称,先在别名和类型映射的map中按照key进行查找,如果找到了直接返回对应的类型,如果没找到,会将这个名称当做完整的类名去解析成Class对象,如果这2步解析都无法识别这种类型,就会报错。

mybatis和别名相关的操作都位于org.apache.ibatis.type.TypeAliasRegistry类中,包含别名的注册、解析等各种操作。

我们来看一下别名解析的方法,如下:

public  Class resolveAlias(String string) {

try {

if (string == null) {

return null;

}

// issue #748

String key = string.toLowerCase(Locale.ENGLISH);

Class value;

if (typeAliases.containsKey(key)) {

value = (Class) typeAliases.get(key);

} else {

value = (Class) Resources.classForName(string);

}

return value;

} catch (ClassNotFoundException e) {

throw new TypeException(“Could not resolve type alias '” + string + "'.  Cause: " + e, e);

}

}

有一个typeAliases对象,我们看一下其定义:

private final Map<String, Class<?>> typeAliases = new HashMap<>();

这个对象就是存放别名和具体类型映射关系的,从上面代码中可以看出,通过传入的参数解析对应的类型的时候,会先从typeAliases中查找,如果找不到会调用下面代码:

value = (Class) Resources.classForName(string);

上面这个方法里面具体是使用下面代码去通过名称解析成类型的:

Class.forName(类名完整名称)

Class.forName大家应该是很熟悉的,可以获取一个字符串对应的Class对象,如果找不到这个对象,会报错。

别名使用建议

别名的方式可以简化类型的写法,原本很长一串的UserModel对象,现在只用写个user就行了,用起来是不是挺爽的?

从写法上面来说,确实少帮我们省了一些代码,但是从维护上面来讲,不是很方便。

如Mapper xml直接写别名,看代码的时候,很难知道这个别名对应的具体类型,还需要我们去注册的地方找一下,不是太方便,如果我们在idea中写完整的类名,还可以按住Ctrl健,然后用鼠标左键点击类型直接可以跳到对应的类定义中去,如果使用别名是无法导航过去的。

整体上来说开发和看代码都不是太方便,只是写法上比价简单。

所以建议自定义的类尽量别使用别名,而对mybatis中内置的一些别名我们需要知道。

属性配置文件详解

大家看一下chat03\src\main\resources\demo1\mybatis-config.xml中下面这一部分的配置:

这个连接数据库的配置,我们是直接写在mybatis全局配置文件中的,上面这是我们本地测试库的db信息,上线之后,需要修改为线上的db配置信息,db配置信息一般由运维去修改,让运维去修改这个xml配置文件?

这样不是太好,我们通常将一些需要运维修改的配置信息(如:db配置、邮件配置、redis配置等等各种配置)放在一个properties配文件中,然后上线时,只需要运维去修改这个配置文件就可以了,根本不用他们去修改和代码相关的文件。

mybatis也支持我们通过外部properties文件来配置一些属性信息。

mybatis配置属性信息有3种方式。

方式1:property元素中定义属性
属性定义

mybatis全局配置文件中通过properties元素来定义属性信息,如下:

上面通过property元素的方式进行配置属性信息:

name:属性的名称

value:属性的值。

如:

使用${属性名称}引用属性的值

属性已经定义好了,我们可以通过${属性名称}引用定义好的属性的值,如:

案例

我们在demo1/mapper/mybatis-config.xmlconfiguration元素中加入下面配置:

修改datasource的配置:

运行com.javacode2018.chat03.demo1.UserMapperTest#getUserList,如下:

40:22.274 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==>  Preparing: SELECT * FROM t_user

40:22.307 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:

40:22.330 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <==      Total: 4

40:22.331 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)

40:22.332 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)

40:22.332 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)

40:22.332 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)

运行正常。

方式2:resource引入配置文件

方式1中,我们的配置文件还是写在全局配置文件中,mybatis支持从外部引入配置文件,可以把配置文件写在其他外部文件中,然后进行引入。

引入classes路径中的配置文件

properties元素有个resource属性,值为配置文件相对于classes的路径,配置文件我们一般放在src/main/resource目录,这个目录的文件编译之后会放在classes路径中。

案例

下面我们将上面db的配置放在外部的config.properties文件中。

chat03\src\main\resources\demo1目录新建一个配置文件config.properties,内容如下:

jdbc.driver=com.mysql.jdbc.Driver

jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8

jdbc.username=root

jdbc.password=root123

demo1/mapper/mybatis-config.xml中引入上面配置文件:

目前demo1/mapper/mybatis-config.xml文件内容如下:

<?xml version="1.0" encoding="UTF-8" ?>

运行com.javacode2018.chat03.demo1.UserMapperTest#getUserList,如下:

57:40.405 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==>  Preparing: SELECT * FROM t_user

57:40.436 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:

57:40.454 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <==      Total: 4

57:40.455 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)

57:40.457 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)

57:40.457 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)

57:40.457 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)

运行正常。

方式3:url的方式引入远程配置文件

mybatis还提供了引入远程配置文件的方式,如下:

这次还是使用properties元素,不过使用的是url属性,如:

这种方式的案例就不提供了,有兴趣的可以自己去玩玩。

属性配置文件使用建议

上面我们说了3中方式,第2中方式是比较常见的做法,建议大家可以使用第二种方式来引入外部资源配置文件。

问题

如果3种方式如果我们都写了,mybatis会怎么走?

下面我们修改一下resources/demo1/mybatis-config.xml,使用第一种方式定义属性,如下:

password的值改为了root,正确的是root123,运行测试用例,报错如下:

org.apache.ibatis.exceptions.PersistenceException:

### Error querying database.  Cause: java.sql.SQLException: Access denied for user ‘root’@‘localhost’ (using password: YES)

### The error may exist in demo1/mapper/UserMapper.xml

### The error may involve com.javacode2018.chat03.demo1.UserMapper.getUserList

### The error occurred while executing a query

### Cause: java.sql.SQLException: Access denied for user ‘root’@‘localhost’ (using password: YES)

at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)

at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149)

at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)

at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147)

at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80)

at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:93)

at com.sun.proxy.$Proxy6.getUserList(Unknown Source)

at com.javacode2018.chat03.demo1.UserMapperTest.getUserList(UserMapperTest.java:38)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:498)

at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)

at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)

at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)

at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)

at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)

at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)

at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)

at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)

at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)

at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)

at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)

at org.junit.runners.ParentRunner.run(ParentRunner.java:363)

at org.junit.runner.JUnitCore.run(JUnitCore.java:137)

at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)

at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)

at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)

at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Caused by: java.sql.SQLException: Access denied for user ‘root’@‘localhost’ (using password: YES)

提示密码错误。

下面我们将第2种方式也加入,修改配置:

再运行一下测试用例,如下:

18:59.436 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==>  Preparing: SELECT * FROM t_user

18:59.462 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - ==> Parameters:

18:59.481 [main] DEBUG c.j.c.demo1.UserMapper.getUserList - <==      Total: 4

18:59.482 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)

18:59.485 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)

18:59.485 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)

18:59.485 [main] INFO  c.j.chat03.demo1.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)

这次正常了。

可以看出方式1和方式2都存在的时候,方式2的配置会覆盖方式1的配置。

mybatis这块的源码在org.apache.ibatis.builder.xml.XMLConfigBuilder#propertiesElement方法中,如下:

private void propertiesElement(XNode context) throws Exception {

if (context != null) {

Properties defaults = context.getChildrenAsProperties();

String resource = context.getStringAttribute(“resource”);

String url = context.getStringAttribute(“url”);

if (resource != null && url != null) {

throw new BuilderException(“The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.”);

}

if (resource != null) {

defaults.putAll(Resources.getResourceAsProperties(resource));

} else if (url != null) {

defaults.putAll(Resources.getUrlAsProperties(url));

}

Properties vars = configuration.getVariables();

if (vars != null) {

defaults.putAll(vars);

}

parser.setVariables(defaults);

configuration.setVariables(defaults);

}

}

从上面代码中也可以看出,如果方式2和方式3都存在的时候,方式3会失效,mybatis会先读取方式1的配置,然后读取方式2或者方式3的配置,会将1中相同的配置给覆盖。

mybatis中引入mapper的3种方式

mapper xml文件是非常重要的,我们写的sql基本上都在里面,使用mybatis开发项目的时候,和mybatis相关的大部分代码就是写sql,基本上都是和mapper xml打交道。

编写好的mapper xml需要让mybatis知道,我们怎么让mybatis知道呢?

可以通过mybatis全局配置文件进行引入,主要有3种方式。

方式1:使用mapper resouce属性注册mapper xml文件

目前我们所涉及到的各种例子都是采用的这种方式,使用下面的方法进行引入:

再来说一下这种方式的一些注意点:

  1. 一般情况下面我,我们会创建一个和Mapper xml中namespace同名的Mapper接口,Mapper接口会和Mapper xml文件进行绑定

  2. mybatis加载mapper xml的时候,会去查找namespace对应的Mapper接口,然后进行注册,我们可以通过Mapper接口的方式去访问Mapper xml中的具体操作

  3. Mapper xml和Mapper 接口配合的方式是比较常见的做法,也是强烈建议大家使用的

方式2:使用mapper class属性注册Mapper接口
引入Mapper接口

mybatis全局配置文件中引入mapper接口,如下:

这种情况下,mybais会去加载class对应的接口,然后还会去加载和这个接口同一个目录的同名的xml文件。

如:

上面这种写法,mybatis会自动去注册UserMapper接口,还会去查找下面的文件:

com/javacode2018/chat03/demo1/UserMapper.xml

大家以后开发项目的时候估计也会看到这种写法,Mapper接口Mapper xml文件放在同一个包中。

案例

下面我们重新创建一个案例,都放在demo2包中。

新建com.javacode2018.chat03.demo2.UserModel,如下:

package com.javacode2018.chat03.demo2;

import lombok.*;

import org.apache.ibatis.type.Alias;

/**

* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!

*/

@Getter

@Setter

@NoArgsConstructor

@AllArgsConstructor

@Builder

@ToString

public class UserModel {

private Long id;

private String name;

private Integer age;

private Double salary;

private Integer sex;

}

新建com.javacode2018.chat03.demo2.UserMapper,如下:

package com.javacode2018.chat03.demo2;

import java.util.List;

/**

* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!

*/

public interface UserMapper {

List getUserList();

}

chat03\src\main\java\com\javacode2018\chat03\demo2中创建UserMapper.xml,如下:

<?xml version="1.0" encoding="UTF-8" ?> <![CDATA[ SELECT * FROM t_user ]]>

下面重点来了。

创建mybatis全局配置文件,在chat03\src\main\resources\demo2目录中创建mybatis-config.xml,如下:

<?xml version="1.0" encoding="UTF-8" ?>

chat03\src\test\java目录创建测试用例com.javacode2018.chat03.demo2.UserMapperTest,如下:

package com.javacode2018.chat03.demo2;

import lombok.extern.slf4j.Slf4j;

import org.apache.ibatis.io.Resources;

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import org.junit.Before;

import org.junit.Test;

import java.io.IOException;

import java.io.InputStream;

import java.util.List;

/**

* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!

*/

@Slf4j

public class UserMapperTest {

private SqlSessionFactory sqlSessionFactory;

@Before

public void before() throws IOException {

//指定mybatis全局配置文件

String resource = “demo2/mybatis-config.xml”;

//读取全局配置文件

InputStream inputStream = Resources.getResourceAsStream(resource);

//构建SqlSessionFactory对象

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

this.sqlSessionFactory = sqlSessionFactory;

}

@Test

public void getUserList() {

try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true)😉 {

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

//执行查询操作

List userModelList = mapper.getUserList();

userModelList.forEach(item -> {

log.info(“{}”, item);

});

}

}

}

注意这次上面使用的是demo2/mybatis-config.xml配置文件。

我们先来看一下项目结构,4个文件:

注意一下UserMapper接口所在的包中有个同名的UserMapper.xml文件,这个如果按照方式2中所说的,会自动加载。

下面我们来运行一下com.javacode2018.chat03.demo2.UserMapperTest#getUserList,输出:

org.apache.ibatis.binding.BindingException: Type interface com.javacode2018.chat03.demo2.UserMapper is not known to the MapperRegistry.

at org.apache.ibatis.binding.MapperRegistry.getMapper(MapperRegistry.java:47)

at org.apache.ibatis.session.Configuration.getMapper(Configuration.java:779)

at org.apache.ibatis.session.defaults.DefaultSqlSession.getMapper(DefaultSqlSession.java:291)

at com.javacode2018.chat03.demo2.UserMapperTest.getUserList(UserMapperTest.java:36)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:498)

at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)

at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)

at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)

at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)

at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)

at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)

at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)

at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)

at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)

at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)

at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)

at org.junit.runners.ParentRunner.run(ParentRunner.java:363)

at org.junit.runner.JUnitCore.run(JUnitCore.java:137)

at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)

at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)

at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)

at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

从输出中可以看到,UserMapper找不到。

我们去看一下demo2/mybatis-config.xml这个配置文件,这个文件中需要使用方式2引入UserMapper接口,在demo2/mybatis-config.xml中加入下面配置:

再运行一下,还是报错,如下,还是找不到对应的UserMapper:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.javacode2018.chat03.demo2.UserMapper.getUserList

at org.apache.ibatis.binding.MapperMethod$SqlCommand.(MapperMethod.java:235)

at org.apache.ibatis.binding.MapperMethod.(MapperMethod.java:53)

还是有问题,我们看一下target/classesdemo2包的内容,如下图:

编译之后的文件中少了UserMapper.xml,这个和maven有关,maven编译src/java代码的时候,默认只会对java文件进行编译然后放在target/classes目录,需要在chat03/pom.xml中加入下面配置:

${project.basedir}/src/main/java

**/*.xml

${project.basedir}/src/main/resources

**/*

最终chat03/pom.xml内容如下:

<?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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>

mybatis-series

com.javacode2018

1.0-SNAPSHOT

4.0.0

chat03

org.mybatis

mybatis

mysql

mysql-connector-java

org.projectlombok

lombok

junit

junit

ch.qos.logback

logback-classic

${project.basedir}/src/main/java

**/*.xml

${project.basedir}/src/main/resources

**/*

加了这个之后UserMapper.xml就会被放到target的classes中去了,如下图:

为什么maven中需要加上面配置,这块大家可以去看公众号中maven系列的文章,里面有详细介绍,maven的相关东西,后面还会经常用到,对这块不熟悉的,建议尽快把maven系列的所有文章都看一遍,以免后面学习的过程中掉队。

我们再次运行一下测试用例com.javacode2018.chat03.demo2.UserMapperTest#getUserList,效果如下:

24:37.814 [main] DEBUG c.j.c.demo2.UserMapper.getUserList - ==>  Preparing: SELECT * FROM t_user

24:37.852 [main] DEBUG c.j.c.demo2.UserMapper.getUserList - ==> Parameters:

24:37.875 [main] DEBUG c.j.c.demo2.UserMapper.getUserList - <==      Total: 4

24:37.876 [main] INFO  c.j.chat03.demo2.UserMapperTest - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)

24:37.879 [main] INFO  c.j.chat03.demo2.UserMapperTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)

24:37.879 [main] INFO  c.j.chat03.demo2.UserMapperTest - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)

24:37.879 [main] INFO  c.j.chat03.demo2.UserMapperTest - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)

这次正常了。

源码

方式2对应的源码大家可以去看下面这个方法:

org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement

方法中会去加载mapper元素中class属性指定的Mapper接口,然后进行注册,随后会在接口同目录中查找同名的mapper xml文件,将解析这个xml文件,如果mapper xml文件不存在,也不会报错,源码还是比较简单的,大家可以去看一下,加深理解。

方式3:使用package元素批量注册Mapper接口
批量注册Mapper接口

上面说2种方式都是一个个注册mapper的,如果我们写了很多mapper,是否能够批量注册呢?

mybatis提供了扫描包批量注册的方式,需要在mybatis全局配置文件中加入下面配置:

mybatis会扫描package元素中name属性指定的包及子包中的所有接口,将其当做Mapper 接口进行注册,所以一般我们会创建一个mapper包,里面放Mapper接口同名的Mapper xml文件

大家来看一个案例,理解一下。

案例

这个案例中将对t_user、t_order两个表进行查询操作,采用方式3中的package批量引入mapper 接口和xml文件。

所有代码放在demo3包中,大家先看下文件所在的目录:

创建UserModel类,如下:

package com.javacode2018.chat03.demo3.model;

import lombok.*;

/**

* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!

*/

@Getter

@Setter

@NoArgsConstructor

@AllArgsConstructor

@Builder

@ToString

public class UserModel {

private Long id;

private String name;

private Integer age;

private Double salary;

private Integer sex;

}

创建OrderModel类,如下:

package com.javacode2018.chat03.demo3.model;

import lombok.*;

/**

* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!

*/

@Getter

@Setter

@NoArgsConstructor

@AllArgsConstructor

@Builder

@ToString

public class OrderModel {

private Long id;

private Long user_id;

private Double price;

}

创建UserMapper接口,如下:

package com.javacode2018.chat03.demo3.mapper;

import com.javacode2018.chat03.demo3.model.UserModel;

import java.util.List;

/**

* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!

*/

public interface UserMapper {

List getList();

}

创建OrderMapper接口,如下:

package com.javacode2018.chat03.demo3.mapper;

import com.javacode2018.chat03.demo3.model.OrderModel;

import com.javacode2018.chat03.demo3.model.UserModel;

import java.util.List;

/**

* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!

*/

public interface OrderMapper {

List getList();

}

创建UserMapper.xml,如下:

<?xml version="1.0" encoding="UTF-8" ?> <![CDATA[ SELECT * FROM t_order ]]>

上面我们写了一个查询t_user数据的sql

创建OrderMapper.xml,如下:

<?xml version="1.0" encoding="UTF-8" ?> <![CDATA[ SELECT * FROM t_order ]]>

上面我们写了一个查询t_order数据的sql

创建resources/demo3/mybatis-config.xml配置文件,如下:

<?xml version="1.0" encoding="UTF-8" ?>

注意这次我们使用package来让mybatis加载com.javacode2018.chat03.demo3.mapper包下面所有的Mapper接口和Mapper xml文件。

创建测试用例Demo3Test,如下:

package com.javacode2018.chat03.demo3;

import com.javacode2018.chat03.demo3.mapper.OrderMapper;

import com.javacode2018.chat03.demo3.mapper.UserMapper;

import com.javacode2018.chat03.demo3.model.OrderModel;

import com.javacode2018.chat03.demo3.model.UserModel;

import lombok.extern.slf4j.Slf4j;

import org.apache.ibatis.io.Resources;

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import org.junit.Before;

import org.junit.Test;

import java.io.IOException;

import java.io.InputStream;

import java.util.List;

/**

* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!

*/

@Slf4j

public class Demo3Test {

private SqlSessionFactory sqlSessionFactory;

@Before

public void before() throws IOException {

//指定mybatis全局配置文件

String resource = “demo3/mybatis-config.xml”;

//读取全局配置文件

InputStream inputStream = Resources.getResourceAsStream(resource);

//构建SqlSessionFactory对象

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

this.sqlSessionFactory = sqlSessionFactory;

}

@Test

public void test() {

try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true)😉 {

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

//执行查询操作

List userModelList = userMapper.getList();

userModelList.forEach(item -> {

log.info(“{}”, item);

});

log.info(“----------------------------------”);

OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);

//执行查询操作

List orderModelList = orderMapper.getList();

orderModelList.forEach(item -> {

log.info(“{}”, item);

});

}

}

}

运行com.javacode2018.chat03.demo3.Demo3Test#test,输出如下:

48:39.280 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==>  Preparing: SELECT * FROM t_user

48:39.315 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==> Parameters:

48:39.339 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - <==      Total: 4

48:39.340 [main] INFO  c.j.chat03.demo3.Demo3Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)

48:39.343 [main] INFO  c.j.chat03.demo3.Demo3Test - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)

48:39.343 [main] INFO  c.j.chat03.demo3.Demo3Test - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)

48:39.343 [main] INFO  c.j.chat03.demo3.Demo3Test - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)

48:39.343 [main] INFO  c.j.chat03.demo3.Demo3Test - ----------------------------------

48:39.344 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - ==>  Preparing: SELECT * FROM t_order

48:39.345 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - ==> Parameters:

48:39.351 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - <==      Total: 2

48:39.351 [main] INFO  c.j.chat03.demo3.Demo3Test - OrderModel(id=1, user_id=1, price=88.88)

48:39.351 [main] INFO  c.j.chat03.demo3.Demo3Test - OrderModel(id=2, user_id=2, price=666.66)

这种批量的方式是不是用着挺爽的,不过有点不是太好,mapper xml和mapper接口放在了一个目录中,目录中既有java代码又有xml文件,看起来也挺别扭的,其实你们可以这样:

一般我们将配置文件放在resource目录,我们可以在resource目录中创建下面子目录:

com/javacode2018/chat03/demo3/mapper

然后将com.javacode2018.chat03.demo3.mapper中的2个xml文件移到上面新创建的目录中去,如下图:

在去运行一下com.javacode2018.chat03.demo3.Demo3Test#test,输出如下:

56:22.669 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==>  Preparing: SELECT * FROM t_user

56:22.700 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==> Parameters:

56:22.721 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - <==      Total: 4

56:22.722 [main] INFO  c.j.chat03.demo3.Demo3Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)

56:22.725 [main] INFO  c.j.chat03.demo3.Demo3Test - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)

56:22.725 [main] INFO  c.j.chat03.demo3.Demo3Test - UserModel(id=3, name=张学友, age=56, salary=500000.0, sex=1)

56:22.725 [main] INFO  c.j.chat03.demo3.Demo3Test - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)

56:22.725 [main] INFO  c.j.chat03.demo3.Demo3Test - ----------------------------------

56:22.727 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - ==>  Preparing: SELECT * FROM t_order

56:22.727 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - ==> Parameters:

56:22.732 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - <==      Total: 2

56:22.732 [main] INFO  c.j.chat03.demo3.Demo3Test - OrderModel(id=1, user_id=1, price=88.88)

56:22.732 [main] INFO  c.j.chat03.demo3.Demo3Test - OrderModel(id=2, user_id=2, price=666.66)

也是可以的。

源码

方式3的源码和方式2的源码在一个地方:

org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement

方法中会去扫描指定的包中所有的接口,会将接口作为Mapper接口进行注册,然后还会找这些接口同名的Xml文件,将其注册为Mapper xml文件,相对于对方式2循环的方式。

使用注意

方式3会扫描指定包中所有的接口,把这些接口作为Mapper接口进行注册,扫描到的类型只要是接口就会被注册,所以指定的包中通常我们只放Mapper接口,避免存放一些不相干的类或者接口。

关于配置和源码

本次讲解到的一些配置都是在mybatis全局配置文件中进行配置的,这些元素配置是有先后顺序的,具体元素是在下面的dtd文件中定义的:

http://mybatis.org/dtd/mybatis-3-config.dtd

建议大家去看一下这个dtd配置文件。

Mybatis解析这个配置文件的入口是在下面的方法中:

org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

代码的部分实现如下:

private void parseConfiguration(XNode root) {

try {

//issue #117 read properties first

propertiesElement(root.evalNode(“properties”));

Properties settings = settingsAsProperties(root.evalNode(“settings”));

loadCustomVfs(settings);

loadCustomLogImpl(settings);

typeAliasesElement(root.evalNode(“typeAliases”));

pluginElement(root.evalNode(“plugins”));

objectFactoryElement(root.evalNode(“objectFactory”));

objectWrapperFactoryElement(root.evalNode(“objectWrapperFactory”));

reflectorFactoryElement(root.evalNode(“reflectorFactory”));

settingsElement(settings);

// read it after objectFactory and objectWrapperFactory issue #631

environmentsElement(root.evalNode(“environments”));

databaseIdProviderElement(root.evalNode(“databaseIdProvider”));

typeHandlerElement(root.evalNode(“typeHandlers”));

mapperElement(root.evalNode(“mappers”));

} catch (Exception e) {

throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);

}

}

可以看到mybatis启动的时候会按顺序加载上面的标签,大家可以去看一下源码,研究一下,下篇继续深入mybatis其他知识点。

总结

  1. 掌握别名注册的3种方式,建议大家尽量少使用自定义别名

  2. 掌握属性配置3种方式

  3. 掌握mapper注册的3种方式及需要注意的地方

最后

小编利用空余时间整理了一份《MySQL性能调优手册》,初衷也很简单,就是希望能够帮助到大家,减轻大家的负担和节省时间。

关于这个,给大家看一份学习大纲(PDF)文件,每一个分支里面会有详细的介绍。

image

这里都是以图片形式展示介绍,如要下载原文件以及更多的性能调优笔记(MySQL+Tomcat+JVM)!
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
at03.demo3.Demo3Test - UserModel(id=4, name=林志玲, age=45, salary=88888.88, sex=2)

56:22.725 [main] INFO  c.j.chat03.demo3.Demo3Test - ----------------------------------

56:22.727 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - ==>  Preparing: SELECT * FROM t_order

56:22.727 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - ==> Parameters:

56:22.732 [main] DEBUG c.j.c.d.mapper.OrderMapper.getList - <==      Total: 2

56:22.732 [main] INFO  c.j.chat03.demo3.Demo3Test - OrderModel(id=1, user_id=1, price=88.88)

56:22.732 [main] INFO  c.j.chat03.demo3.Demo3Test - OrderModel(id=2, user_id=2, price=666.66)

也是可以的。

源码

方式3的源码和方式2的源码在一个地方:

org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement

方法中会去扫描指定的包中所有的接口,会将接口作为Mapper接口进行注册,然后还会找这些接口同名的Xml文件,将其注册为Mapper xml文件,相对于对方式2循环的方式。

使用注意

方式3会扫描指定包中所有的接口,把这些接口作为Mapper接口进行注册,扫描到的类型只要是接口就会被注册,所以指定的包中通常我们只放Mapper接口,避免存放一些不相干的类或者接口。

关于配置和源码

本次讲解到的一些配置都是在mybatis全局配置文件中进行配置的,这些元素配置是有先后顺序的,具体元素是在下面的dtd文件中定义的:

http://mybatis.org/dtd/mybatis-3-config.dtd

建议大家去看一下这个dtd配置文件。

Mybatis解析这个配置文件的入口是在下面的方法中:

org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

代码的部分实现如下:

private void parseConfiguration(XNode root) {

try {

//issue #117 read properties first

propertiesElement(root.evalNode(“properties”));

Properties settings = settingsAsProperties(root.evalNode(“settings”));

loadCustomVfs(settings);

loadCustomLogImpl(settings);

typeAliasesElement(root.evalNode(“typeAliases”));

pluginElement(root.evalNode(“plugins”));

objectFactoryElement(root.evalNode(“objectFactory”));

objectWrapperFactoryElement(root.evalNode(“objectWrapperFactory”));

reflectorFactoryElement(root.evalNode(“reflectorFactory”));

settingsElement(settings);

// read it after objectFactory and objectWrapperFactory issue #631

environmentsElement(root.evalNode(“environments”));

databaseIdProviderElement(root.evalNode(“databaseIdProvider”));

typeHandlerElement(root.evalNode(“typeHandlers”));

mapperElement(root.evalNode(“mappers”));

} catch (Exception e) {

throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);

}

}

可以看到mybatis启动的时候会按顺序加载上面的标签,大家可以去看一下源码,研究一下,下篇继续深入mybatis其他知识点。

总结

  1. 掌握别名注册的3种方式,建议大家尽量少使用自定义别名

  2. 掌握属性配置3种方式

  3. 掌握mapper注册的3种方式及需要注意的地方

最后

小编利用空余时间整理了一份《MySQL性能调优手册》,初衷也很简单,就是希望能够帮助到大家,减轻大家的负担和节省时间。

关于这个,给大家看一份学习大纲(PDF)文件,每一个分支里面会有详细的介绍。

[外链图片转存中…(img-BLOU2jLK-1714708933980)]

这里都是以图片形式展示介绍,如要下载原文件以及更多的性能调优笔记(MySQL+Tomcat+JVM)!
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值