目录
(一)项目文件结构及创建流程
(二)常见错误及原因分析
1、Mysql版本差异引起的错误
MYsql 8 连接报错 MySQLNonTransientConnectionException: Could not create connection to database server https://www.cnblogs.com/Jordandan/p/11249236.html
2、Mysql8+存在的时区文体
InvalidConnectionAttributeException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or https://blog.csdn.net/aigov/article/details/90202671
3、静态资源加载文体
Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource xxx https://blog.csdn.net/wt_better/article/details/90261220
(三)基础功能相关创建
1、SQLSession的两种创建及使用
SqlSessions 是由 SqlSessionFactory 实例创建的。SqlSessionFactory 对象包含创建 SqlSession 实例的各种方法。而 SqlSessionFactory 本身是由 SqlSessionFactoryBuilder 创建的,它可以从 XML、注解或 Java 配置代码来创建 SqlSessionFactory。
SqlSessionFactoryBuilder、SqlSessionFactory、SqlSessions这三个关键的对象他们的作用不同,所以对应的最佳的声明周期自然也就不同,在了解他们声明周期的基础上我们可以写出更好的代码结构:
- SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。
创建一个工具类用来生成sqlSession,他以工具类的形式存在于整个应用作用域,这里我们也可以同时创建多个类似于getSession()的方法用来生成拥有不同配置环境的sqlSession:
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
//在类加载的时候只被执行一次,创建出来了一个sqlSessionFactory,而SqlSessionFactoryBuilder
//更是可以看做一个临时的对象
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//获取SqlSession连接
public static SqlSession getSession(){
return sqlSessionFactory.openSession(true);
}
}
官网上给出的两种用来创建sqlSession的方法,他们都可以保证sqlSession可以及时的被关闭,而不需要手工的调用close()方法,这两种方法的sqlSession的获取方式都是一样的都是借用了try的传参形式,不同之处在于Mapper的使用:
//旧版本Mybatis写法
try(SqlSession session = MybatisUtils.getSession()){
//这里有两种写法如果我们最后确定的名称全局只有一个那么也是可以但是如果在不同的局部有多个的话那就需要使用全限定类名以避免冲突
//List<User> users = session.selectList("com.example.mybatiscode.workspace01.mapper.UserMapper.selectUser");
List<User> users = session.selectList("selectUser");
for(User user:users){
System.out.println(user);
}
System.out.println("try代码体这就结束");
}
System.out.println("新的执行体这就要开始了");
//官网新版本的写法
try(SqlSession session = MybatisUtils.getSession()){
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> users = userMapper.selectUser();
for (User user:users){
System.out.println(user);
}
}
需要手动关闭的,如果在使用后没有使用close(),Connection将不会关闭:
//新版本常规写法
SqlSession session = MybatisUtils.getSession();
UserMapper userDao = session.getMapper(UserMapper.class);
List<User> users = userDao.selectUser();
for (User user: users){
System.out.println(user);
}
//session.close();
2、三种属性来源的优先级与默认值属性
如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
- 首先读取在 properties 元素体内指定的属性。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
3、类型处理器的使用事项
类型处理器 | Java 类型 | JDBC 类型 |
---|---|---|
BooleanTypeHandler | java.lang.Boolean , boolean | 数据库兼容的 BOOLEAN |
ShortTypeHandler | java.lang.Short , short | 数据库兼容的 NUMERIC 或 SMALLINT |
IntegerTypeHandler | java.lang.Integer , int | 数据库兼容的 NUMERIC 或 INTEGER |
StringTypeHandler | java.lang.String | CHAR , VARCHAR |
ClobTypeHandler | java.lang.String | CLOB , LONGVARCHAR |
NStringTypeHandler | java.lang.String | NVARCHAR , NCHAR |
NClobTypeHandler | java.lang.String | NCLOB |
EnumTypeHandler | Enumeration Type | VARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值) |
EnumOrdinalTypeHandler | Enumeration Type | 任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称)。 |
SqlxmlTypeHandler | java.lang.String | SQLXML |
SqlTimestampTypeHandler | java.sql.Timestamp | TIMESTAMP |
MyBatis 不会通过检测数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明字段是 VARCHAR 类型, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。实现 org.apache.ibatis.type.TypeHandler
接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler
, 并且可以(可选地)将它映射到一个 JDBC 类型。
我们可以在mybatis的配置文件中明确的指定处理器,也可以通过指定一个包,让系统自己去寻找合适的类型处理器,在使用自动发现功能的时候,只能通过注解方式来指定 JDBC 的类型:
<!-- mybatis-config.xml 明确指定 -->
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
<!-- mybatis-config.xml 自动寻找 -->
<typeHandlers>
<package name="org.mybatis.example"/>
</typeHandlers>
自定义类型处理器实现的一个实例:
// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:
- 在类型处理器的配置元素(typeHandler 元素)上增加一个
javaType
属性(比如:javaType="String"
); - 在类型处理器的类上增加一个
@MappedTypes
注解指定与其关联的 Java 类型列表。 如果在javaType
属性中也同时指定,则注解上的配置将被忽略。
可以通过两种方式来指定关联的 JDBC 类型:
- 在类型处理器的配置元素上增加一个
jdbcType
属性(比如:jdbcType="VARCHAR"
); - 在类型处理器的类上增加一个
@MappedJdbcTypes
注解指定与其关联的 JDBC 类型列表。 如果在jdbcType
属性中也同时指定,则注解上的配置将被忽略。
当在 ResultMap
中决定使用哪种类型处理器时,此时 Java 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 javaType=[Java 类型], jdbcType=null
的组合来选择一个类型处理器。 这意味着使用 @MappedJdbcTypes
注解可以限制类型处理器的作用范围,并且可以确保,除非显式地设置,否则类型处理器在 ResultMap
中将不会生效。 如果希望能在 ResultMap
中隐式地使用类型处理器,那么设置 @MappedJdbcTypes
注解的 includeNullJdbcType=true
即可。 然而从 Mybatis 3.4.0 开始,如果某个 Java 类型只有一个注册的类型处理器,即使没有设置 includeNullJdbcType=true
,那么这个类型处理器也会是 ResultMap
使用 Java 类型时的默认处理器。
4、映射器的几种配置方式
5、环境配置的相关信息
6、数据库供应商的属性的相关应用
7、基于xml文件的自增长主键返回
在利用这些特殊的属性来做自增长自动返回应该注意我们这里设置的自增长字段在数据库表设计的时候也应该是设置称为自增长,否则的话会报错;如果SQLSession没有及时的提交的话,数据是不能够保存成功的,虽然主键是一直在自增长的。
测试代码与接口编写:
@Test
public void testGeneratedKeys(){
try(SqlSession sqlSession = MybatisUtils.getSession()){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
ArrayList<User> userArrayList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
userArrayList.add(new User("LiuMenglei","2234"));
}
userMapper.insertUserGeneratedKeys(userArrayList);
for (User user:userArrayList) {
System.out.println(user.toString());
}
sqlSession.commit();
}
}
public interface UserMapper {
List<User> selectUser();
void insertUserGeneratedKeys(ArrayList<User> userArrayList);
}
XML相关配置:
<insert id="insertUserGeneratedKeys" useGeneratedKeys="true"
keyProperty="id">
insert into user (name, pwd) values
<foreach item="item" collection="list" separator=",">
(#{item.name}, #{item.pwd})
</foreach>
</insert>
<!-- 自增长单值返回示例-->
<insert id="insertUserGeneratedKeys"
parameterType="com.example.mybatiscode.workspace01.domain.User"
useGeneratedKeys="true" keyProperty="id">
insert into user (name,pwd) values (#{name},#{pwd})
</insert>
对于那些不支持自增长主键的驱动mybatis也可以通过一种类似于生成随机字符串的范式来处理,但是并不建议使用这样的方式。
8、SQL语句的复用
在使用sql这个标签的时候我们 应该注意占位符用的不是#而是$,这和我们在上面中用到的占位符是不一样的,这一点应该格外注意,不然的话会报错:
<sql id = "sql1">${alias}.id,${alias}.name,${alias}.pwd</sql>
<select id="repeatSql" resultType="com.example.mybatiscode.workspace01.domain.User">
select
<include refid="sql1">
<property name="alias" value="u1"/>
</include>
from user u1
</select>
复用的sql语句中也是可以通过参数进行值传递的,这样的写法个人感觉和前端中插槽的应用有着异曲同工之妙。
9、字符串替换
默认情况下,使用 #{}
参数语法时,MyBatis 会创建 PreparedStatement
参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。这里#、$的使用牵扯到的是Mysql的底层一些的东西,在直接用Java 的JDBC来进行SQL语句的使用时存在?问题的;从这方面来说的话#并不只是一个简单的变量功能,而$可以看成是起到一个单纯的变量的作用。当我们想要根据表中的每一行来惊醒筛选我们可以利用$的这个语法:
在做这些的时候应该注意方法属性名与xml文件中拼写的名称的一致,否则的话会报错
<select id = "selectBySingleCondition" resultType="com.example.mybatiscode.workspace01.domain.User">
select * from user where ${columnname} = #{condition}
</select>
@Test
public void selectBySingleContion(){
try(SqlSession sqlSession = MybatisUtils.getSession()){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectBySingleCondition("name","liumenglei");
for (User user:userList) {
System.out.println(user.toString());
}
sqlSession.commit();
}
}
当我们面临的是比较简单的语句时,我们可以直接通过注解的方式来进行开发:
@Select("select * from user where ${name} = #{condi}")
List<User> selectBySingleCondition(@Param("name") String columnname, @Param("condi") String condition);
10、ResultMap高级映射的使用
11、自动映射与手工配置的搭配
Mybatis在进行自动映射的时候他对列名以及Java属性的匹配是忽略大小写的,同时当我们数据库种使用下划线的写法Java找那个使用驼峰命名的方式时也是可以通过相关的配置类开启他们之间的匹配。
自动映射的使用实际上还是匹配数据库中列的名字,这时候如果我们给列起别名这也是可以起到自动映射的效果的,这样的话会减少我们在映射匹配时候的代码量。如果只是有个别的列与属性不能够通过名字进行映射的话我们可以只配置这些个别的手动映射。
12、动态SQL的使用技巧
动态sql中我们使用的也是循环判断的编程思想,在mybatis中农使用动态sql的意义在于可以通过动态SQL来减少在我们认为编写动态SQL时的一些关键字位置错误,分隔符多加少加的问题。我们用到的关键字是if、choose、trim、foreach。if是一种较为简单的使用,在面对复杂的应用场景时也时没有足够的能力处理的:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
这里指的注意的一个就是有关author对象的判断,以及author内部属性值是否为空的判断,这是一种较为安全的写法。 在Mybatis的动态SQL中choose变迁可以起到我们其他语言中常见的switch语法的效果:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
但是如果我们见state的这个判断条件也加入到if的标签中,当state属性为空的时候就可能会出现where后面直接跟and这是一种错误的写法这时候可以采用where的标签,where标签是用来做条件判定的,还有一个和where有着相同功能的就是set标签,这两个标签都可以动态的拼接我们中间的sql从而避免出现上面提到的关键词多余分隔符多余等错误:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
针对动态sql所拥有的能力我们还可以通过trim属性做一个更加个性化的定制,他相当于可以让我们自定义各种规则,但是个人认为where、set标签的能力就已经够我们日常开发使用了:
前缀、前缀覆盖、后缀覆盖;在这个例子中给出的前缀覆盖管道符前面的空格是有必要的;
<trim prefix="WHERE" prefixOverrides="AND |OR " suffixOverrides=",">
...
</trim>
当我们需要进行批量的插入时候就要遇到foreach语句了,他的属性都是容易理解的一些意思,其中index,单签元素在数组或是集合中的小标索引:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
上面介绍的动态SQL都是在xml文件中配置实现的,这些动态SQL的实现我们也可以在注解中实现使用的是<script>标签来实现,同时也可以通过<bind>标签在一定范围内绑定一个变量:
@Update({"<script>",
"update Author",
" <set>",
" <if test='username != null'>username=#{username},</if>",
" <if test='password != null'>password=#{password},</if>",
" <if test='email != null'>email=#{email},</if>",
" <if test='bio != null'>bio=#{bio}</if>",
" </set>",
"where id=#{id}",
"</script>"})
void updateAuthorValues(Author author);
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
13、指定特定的sql连接环境
在我们mybatis的xml配置文件中可能存在诸多的environment,这些不同的environment我们可以在创建SQLSession的时候显现出来,
SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)
SQL执行的过程中存在事务控制,自动提交等信息,然而这些性能指标都是一sqlSession为基本单元的,通过在创建sqlSession的时候通过参数赋予它一定的特性就可以实现一个自定义的效果:
SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();
默认的 openSession() 方法没有参数,它会创建具备如下特性的 SqlSession:
- 事务作用域将会开启(也就是不自动提交)。
- 将由当前环境配置的 DataSource 实例中获取 Connection 对象。
- 事务隔离级别将会使用驱动或数据源的默认设置。
- 预处理语句不会被复用,也不会批量处理更新。
向 autoCommit
可选参数传递 true
值即可开启自动提交功能。若要使用自己的 Connection
实例,传递一个 Connection
实例给 connection
参数即可。注意,我们没有提供同时设置 Connection
和 autoCommit
的方法,这是因为 MyBatis 会依据传入的 Connection 来决定是否启用 autoCommit。对于事务隔离级别,MyBatis 使用了一个 Java 枚举包装器来表示,称为 TransactionIsolationLevel
,事务隔离级别支持 JDBC 的五个隔离级别(NONE
、READ_UNCOMMITTED
、READ_COMMITTED
、REPEATABLE_READ
和 SERIALIZABLE
),并且与预期的行为一致。
你可能对 ExecutorType
参数感到陌生。这个枚举类型定义了三个值:
ExecutorType.SIMPLE
:该类型的执行器没有特别的行为。它为每个语句的执行创建一个新的预处理语句。ExecutorType.REUSE
:该类型的执行器会复用预处理语句。ExecutorType.BATCH
:该类型的执行器会批量执行所有更新语句,如果 SELECT 在多个更新中间执行,将在必要时将多条更新语句分隔开来,以方便理解。
提示 在 SqlSessionFactory 中还有一个方法我们没有提及,就是 getConfiguration()。这个方法会返回一个 Configuration 实例,你可以在运行时使用它来检查 MyBatis 的配置。
提示 如果你使用过 MyBatis 的旧版本,可能还记得 session、事务和批量操作是相互独立的。在新版本中则不是这样。上述三者都包含在 session 作用域内。你不必分别处理事务或批量操作就能得到想要的全部效果。
14、sqlSession的能力
SqlSession 在 MyBatis 中是非常强大的一个类。它包含了所有执行语句、提交或回滚事务以及获取映射器实例的方法。