MyBatis 教程 ——检视阅读

准备

官网文档-中文

教程地址yiibai,质量很差

教程地址w3cschool,纯理论,还不如直接看官网文档

教程地址Mybatis框架入门教程,Oracle

Maven官网

maven pom依赖搜索

回答

  1. 这个教程大体在讲什么?
  2. 教程细部说了什么?
  3. 是否同意教程的知识和观点,同意程度?
  4. 该教程为了带来了什么?

前言

MyBatis 消除 了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。

每个MyBatis应用程序主要都是使用SqlSessionFactory实例的,一个SqlSessionFactory实例可以通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类的实例获得。

用xml文件构建SqlSessionFactory实例是非常简单的事情。推荐在这个配置中使用类路径资源(classpath resource),但你可以使用任何Reader实例,包括用文件路径或file://开头的url创建的实例。MyBatis有一个实用类----Resources,它有很多方法,可以方便地从类路径及其它位置加载资源。

MyBatis 最强大的特性之一就是它的动态语句功能。如果您以前有使用JDBC或者类似框架的经历,您就会明白把SQL语句条件连接在一起是多么的痛苦,要确保不能忘记空格或者不要在columns列后面省略一个逗号等。

MyBatis环境配置搭建项目

上来直接构建一个经典的maven web项目。

前提:

#数据库、表
CREATE DATABASE hello_mybatis; USE hello_mybatis; CREATE TABLE `t_user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(64) NOT NULL COMMENT '姓名',
`dept` varchar(254) NOT NULL COMMENT '部门',
`phone` varchar(16) NOT NULL COMMENT '电话',
`height` decimal(10,2) DEFAULT NULL COMMENT '身高',
`create_emp` bigint(20) NOT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`modify_emp` bigint(20) DEFAULT NULL COMMENT '修改人',
`modify_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表'; INSERT INTO `hello_mybatis`.`t_user` (`name`, `dept`, `phone`, `height`, `create_emp`, `create_time`, `modify_emp`, `modify_time`) VALUES
( '大青山', 'amy empire', '18956563229', '1.85', '1', '2020-03-31 14:17:35', '1', '2020-03-31 14:17:47'); INSERT INTO `hello_mybatis`.`t_user` (`name`, `dept`, `phone`, `height`, `create_emp`, `create_time`, `modify_emp`, `modify_time`) VALUES
( '池寒枫', 'amy empire', '22056545', '1.83', '1', '2020-03-31 14:17:35', '1', '2020-03-31 14:17:47');

代码示例:

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="User" type="com.self.bean.User" />
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/hello_mybatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments> <mappers>
<mapper resource="com/self/dao/UserMapper.xml" />
</mappers>
</configuration>

注意: 对应的是UserMapper.xml在classpath下的地址,后缀.xml要带上(下面的报错就是文件名没有后缀),路径地址要对,否则会报错。

Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/self/dao/UserMapper

UserMapper.xml

CRUD 的sql在各个对应的Mapper 映射文件上写。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.self.dao.UserMapper">
<select id="getUserById" parameterType="int" resultType="User">
select * from `t_user` where id = #{id}
</select>
</mapper>

bean,数据库的映射对象实体类。

public class User {
/**
* id
*/
private int id;
/**
* 名字
*/
private String name;
/**
* 部门,帝国
*/
private String dept;
/**
* 联系号码
*/
private String phone;
/**
* 身高
*/
private BigDecimal height;
/**
* 创建人
*/
private Long createEmp;
/**
* 创建时间
*/
private String createTime;
/**
* 修改人
*/
private Long modifyEmp;
/**
* 修改时间
*/
private String modifyTime; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
}
//其他省略
}

main方法。

public class HelloTest {
private static SqlSessionFactory sqlSessionFactory;
private static Reader reader; static {
try {
reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
}
} public static SqlSessionFactory getSessionFactory(){
return sqlSessionFactory;
} public static void main(String[] args) {
SqlSession sqlSession = getSessionFactory().openSession();
try {
//我们通过调用UserMapper.xml的namespace + id(方法名)来完成数据库访问
User user = (User)sqlSession.selectOne("com.self.dao.UserMapper.getUserById", 1);
System.out.println(JSON.toJSONString(user));
} finally {
sqlSession.close();
}
}
}

输出:

{"dept":"amy empire","height":1.75,"id":1,"name":"艾米","phone":"18956563228"}

配置解释:

  1. 配置文件 mybatis-config.xml 是 mybatis 用来建立 sessionFactory,里面主要包含了数据库连接相关内容,还有 java 类所对应的别名,比如: 这个别名非常重要,在具体的类的映射中,比如:UserMapper.xml 中 resultType 就是对应这个。
  2. mybatis-config.xml 里面 的是包含要映射的类(UserMapper.java)的 xml 配置文件。
  3. 在UserMapper.xml 文件里面主要是定义各种 SQL 语句,以及这些语句的参数,以及要返回的类型等等。

Mybatis接口注解 (项目中一般不使用)

示例:

创建DAO接口

package com.self.dao;

import com.self.bean.User;
import org.apache.ibatis.annotations.Select; public interface UserMapper { @Select("SELECT * FROM t_user where name =#{name}")
public User getUserByName(String name);
}

mybatis-config.xml 配置文件不需要配置mappers,转而在sqlSessionFactory.getConfiguration().addMapper(IUser.class);里添加。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="User" type="com.self.bean.User" />
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/hello_mybatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments> <!--<mappers>-->
<!--<mapper resource="com/self/dao/UserMapper.xml" />-->
<!--</mappers>-->
</configuration>

User 实体类不变。

测试类

public class HelloTest {
private static SqlSessionFactory sqlSessionFactory;
private static Reader reader; static {
try {
reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//在sqlSessionFactory中添加Mapper
sqlSessionFactory.getConfiguration().addMapper(UserMapper.class);
} catch (IOException e) {
e.printStackTrace();
}
} public static SqlSessionFactory getSessionFactory(){
return sqlSessionFactory;
} public static void main(String[] args) {
SqlSession sqlSession = getSessionFactory().openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserByName("大青山");
System.out.println(JSON.toJSONString(user));
} finally {
sqlSession.close();
}
}
}

Mybatis增删改查(CURD)

示例:

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="User" type="com.self.bean.User" />
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/hello_mybatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments> <mappers>
<mapper resource="com/self/dao/UserMapper.xml" />
</mappers>
</configuration>

User 实体类不变。

dao层接口

public interface UserMapper {

    public User getUserById(Integer id);

    public List<User> getUsers();

    public void insert(User user);

    public void update(User user);

    public void deleteById(Integer id);
}

Mapper 映射文件 : src\main\resources\com\self\dao\UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.self.dao.UserMapper">
<select id="getUserById" parameterType="int" resultType="User">
select * from `t_user` where id = #{id}
</select> <select id="getUsers" resultType="User">
select * from `t_user` where 1 = 1
</select> <insert id="insert" parameterType="User">
insert into t_user(`name`, `dept`, `phone`, `height`, `create_emp`, `create_time`, `modify_emp`, `modify_time`) VALUES
(#{name},#{dept},#{phone},#{height},#{createEmp},#{createTime},#{modifyEmp},#{modifyTime})
</insert>
<delete id="deleteById" parameterType="Integer">
delete from t_user where id = #{id}
</delete>
<update id="update" parameterType="User">
update t_user
<set>
<if test="name != null">
name = #{name},
</if>
<if test="dept != null">
dept = #{dept},
</if>
<if test="phone != null">
phone = #{phone},
</if>
<if test="height != null">
height = #{height},
</if>
<if test="modifyEmp != null">
modify_emp = #{modifyEmp},
</if>
<if test="modifyTime != null">
modify_time = #{modifyTime}
</if>
</set>
where id = #{id}
</update>
</mapper>

测试类

package com.self;

import com.alibaba.fastjson.JSON;
import com.self.bean.User;
import com.self.dao.UserMapper;
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 java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List; public class HelloTest {
private static SqlSessionFactory sqlSessionFactory;
private static Reader reader; static {
try {
reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//在sqlSessionFactory中添加Mapper
//sqlSessionFactory.getConfiguration().addMapper(UserMapper.class);
} catch (IOException e) {
e.printStackTrace();
}
} public static SqlSessionFactory getSessionFactory(){
return sqlSessionFactory;
} public static void main(String[] args) {
SqlSession sqlSession = getSessionFactory().openSession();
try {
//我们通过调用UserMapper.xml的namespace + id(方法名)来完成数据库访问
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setCreateEmp(3L);
user.setCreateTime(new Date());
user.setDept("矮人王国");
user.setHeight(new BigDecimal("1.53"));
user.setModifyEmp(3L);
user.setModifyTime(new Date());
user.setPhone("852-253521");
user.setName("霍恩斯");
userMapper.insert(user); User user1 = new User();
user1.setCreateEmp(3L);
user1.setCreateTime(new Date());
user1.setDept("矮人王国");
user1.setHeight(new BigDecimal("1.53"));
user1.setModifyEmp(3L);
user1.setModifyTime(new Date());
user1.setPhone("852-253521");
user1.setName("霍恩斯-克隆");
userMapper.insert(user1);
sqlSession.commit();
System.out.println("--------------------insert---------------------"); User user2 = userMapper.getUserById(4);
System.out.println("-----getUserById----"+JSON.toJSONString(user2)); User updateUser = new User();
updateUser.setDept("森林矮人王国");
updateUser.setHeight(new BigDecimal("1.56"));
updateUser.setModifyEmp(6L);
updateUser.setModifyTime(new Date());
updateUser.setPhone("852-253521");
updateUser.setName("霍恩斯");
updateUser.setId(user2.getId());
userMapper.update(updateUser);
sqlSession.commit();
System.out.println("--------------------update---------------------"); List<User> users = userMapper.getUsers();
System.out.println("----before delete-----"+JSON.toJSONString(users));
userMapper.deleteById(5);
sqlSession.commit();
System.out.println("--------------------deleteById---------------------"); List<User> afterUsers = userMapper.getUsers();
System.out.println("----before delete-----"+JSON.toJSONString(afterUsers));
} finally {
sqlSession.close();
}
}
}

输出:

--------------------insert---------------------
-----getUserById----{"dept":"矮人王国","height":1.53,"id":4,"name":"霍恩斯","phone":"852-253521"}
--------------------update---------------------
----before delete-----[{"dept":"amy empire","height":1.75,"id":1,"name":"艾米","phone":"18956563228"},{"dept":"amy empire","height":1.85,"id":2,"name":"大青山","phone":"18956563229"},{"dept":"amy empire","height":1.83,"id":3,"name":"池寒枫","phone":"22056545"},{"dept":"森林矮人王国","height":1.56,"id":4,"name":"霍恩斯","phone":"852-253521"},{"dept":"矮人王国","height":1.53,"id":5,"name":"霍恩斯-克隆","phone":"852-253521"}]
--------------------deleteById---------------------
----before delete-----[{"dept":"amy empire","height":1.75,"id":1,"name":"艾米","phone":"18956563228"},{"dept":"amy empire","height":1.85,"id":2,"name":"大青山","phone":"18956563229"},{"dept":"amy empire","height":1.83,"id":3,"name":"池寒枫","phone":"22056545"},{"dept":"森林矮人王国","height":1.56,"id":4,"name":"霍恩斯","phone":"852-253521"}]

注意:在增加,更改,删除的时候需要调用 session.commit() 来提交事务,这样才会真正对数据库进行操作提交保存,否则操作没有提交到数据中。

注意:

1、如果在mybatis-config.xml没有配置mapper的xml文件就会报下面的UserMapper is not known to the MapperRegistry.

Exception in thread "main" org.apache.ibatis.binding.BindingException: Type interface com.self.dao.UserMapper is not known to the MapperRegistry.
at org.apache.ibatis.binding.MapperRegistry.getMapper

2、如果插入的数据与字段不匹配个数会报Column count doesn't match value count at row 1。

### SQL: insert into t_user(`name`, `dept`, `phone`, `height`, `create_emp`, `create_time`, `modify_emp`, `modify_time`) VALUES         (?,?,?,?,?,?,?,?,?)
### Cause: java.sql.SQLException: Column count doesn't match value count at row 1

Mybatis表关联一对多

准备:

CREATE TABLE `t_weibo` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` int(10) unsigned NOT NULL COMMENT '用户id',
`title` varchar(254) NOT NULL COMMENT '主题',
`content` text DEFAULT NULL COMMENT '内容',
`create_emp` bigint(20) NOT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`modify_emp` bigint(20) DEFAULT NULL COMMENT '修改人',
`modify_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT= 1000 DEFAULT CHARSET=utf8 COMMENT='微博帖子表'; INSERT INTO `hello_mybatis`.`t_weibo` ( `user_id`, `title`, `content`, `create_emp`, `create_time`, `modify_emp`, `modify_time`)
VALUES ( '4', '霍恩斯描述', '由于矮人种族的原因,他无法成为龙骑士,但这并不影响他在小佣兵团以及整个人神大战中快速成为超级巨星,擅长谋略尤擅兵法让他成为矮人的异类。', '2', '2020-04-01 13:47:24', '2', '2020-04-01 13:47:31'); INSERT INTO `hello_mybatis`.`t_weibo` ( `user_id`, `title`, `content`, `create_emp`, `create_time`, `modify_emp`, `modify_time`)
VALUES ( '4', '霍恩斯与莹', '艾米、大青山、霍恩斯、池傲天、沙若等佣兵团骨干可以逃脱,那些培养了2年多的人才必然全军覆没。', '2', '2020-04-01 13:47:24', '2', '2020-04-01 13:47:31'); INSERT INTO `hello_mybatis`.`t_weibo` ( `user_id`, `title`, `content`, `create_emp`, `create_time`, `modify_emp`, `modify_time`) VALUES
( '2', '大青山描述', '史料记载中第一个神圣巨龙使骑士,在所有的文明中,他都是传统骑士准则的典范,正直、善良、忠诚、守信、怜悯等一切人类美德的拥有者。人龙神三人组合中的一人。在神界受到龙神以黄金凤的胁迫,毅然解除与冰系神圣巨龙使泰穆格尔赛的龙骑士契约,解除龙神的威胁,后与光明神同归于尽。', '2', '2020-04-01 13:47:24', '2', '2020-04-01 13:47:31');

实例:

public class User {
/**
* id
*/
private int id;
/**
* 名字
*/
private String name;
/**
* 部门,帝国
*/
private String dept;
/**
* 联系号码
*/
private String phone;
/**
* 身高
*/
private BigDecimal height; private List<Weibo> weibos;
/**
* 创建人
*/
private Long createEmp;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改人
*/
private Long modifyEmp;
/**
* 修改时间
*/
private Date modifyTime; public List<Weibo> getWeibos() {
return weibos;
} public void setWeibos(List<Weibo> weibos) {
this.weibos = weibos;
}
//其他省略
} public class Weibo { /**
* id
*/
private int id;
/**
* 主题
*/
private String title;
/**
* 内容
*/
private String content;
/**
* 用户信息
*/
private User user;
/**
* 创建人
*/
private Long createEmp;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改人
*/
private Long modifyEmp;
/**
* 修改时间
*/
private Date modifyTime; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
}

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="User" type="com.self.bean.User" />
<typeAlias type="com.self.bean.Weibo" alias="Weibo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/hello_mybatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments> <mappers>
<mapper resource="com/self/dao/UserMapper.xml" />
</mappers>
</configuration>

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.self.dao.UserMapper">
<resultMap id="userMap" type="User">
<result property="id" column="user_id"/>
<result property="name" column="name"/>
<result property="dept" column="dept"/>
<result property="phone" column="phone"/>
<collection property="weibos" ofType="com.self.bean.Weibo" column="user_id">
<!--<id property="id" column="t_weibo.id" javaType="int" jdbcType="INTEGER" />-->
<!--不加表别名或者表名来区分id的话会导致查询不出多条数据-->
<id property="id" column="p.id" javaType="int" jdbcType="INTEGER" />
<result property="title" column="title" javaType="string" jdbcType="VARCHAR"/>
<result property="content" column="content" javaType="string" jdbcType="VARCHAR"/>
</collection>
</resultMap>
<select id="getUserInfoById" resultMap="userMap" parameterType="int">
<!-- SELECT u.*,p.*
FROM t_user u, t_weibo p
WHERE u.id = p.user_id AND u.id=#{id}-->
SELECT u.*,p.*
FROM t_user u
inner join t_weibo p on u.id = p.user_id
WHERE u.id=#{id}
</select>
</mapper> package com.self; import com.alibaba.fastjson.JSON;
import com.self.bean.User;
import com.self.dao.UserMapper;
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 java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List; /**
* Copyright (C), 2015-2020, UCAR.INC
* ClassName: HelloTest
* Author: Rongfeng.Lai
* Date: 2020/3/31 14:42
* Description:
* History:
* <author> <time> <version> <desc>
* Rongfeng.Lai 2020/3/31 v1.1.0 描述
*/
public class HelloTest {
private static SqlSessionFactory sqlSessionFactory;
private static Reader reader; static {
try {
reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//在sqlSessionFactory中添加Mapper
//sqlSessionFactory.getConfiguration().addMapper(UserMapper.class);
} catch (IOException e) {
e.printStackTrace();
}
} public static SqlSessionFactory getSessionFactory(){
return sqlSessionFactory;
} public static void main(String[] args) {
SqlSession sqlSession = getSessionFactory().openSession();
try {
User user = sqlSession.selectOne("com.self.dao.UserMapper.getUserInfoById", 4);
System.out.println("---------"+JSON.toJSONString(user));
} finally {
sqlSession.close();
}
}
}

输出:

---------{"dept":"森林矮人王国","id":4,"name":"霍恩斯","phone":"852-253521","weibos":[{"content":"由于矮人种族的原因,他无法成为龙骑士,但这并不影响他在小佣兵团以及整个人神大战中快速成为超级巨星,擅长谋略尤擅兵法让他成为矮人的异类。","id":4,"title":"霍恩斯描述"}]}

当配置resultMap时jdbcType="TEXT"会报错,要配置成jdbcType="VARCHAR"。

<result property="content" column="content" javaType="string" jdbcType="TEXT"/>
报错:
Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'com/self/dao/UserMapper.xml'. Cause: org.apache.ibatis.builder.BuilderException: Error resolving JdbcType. Cause: java.lang.IllegalArgumentException: No enum constant org.apache.ibatis.type.JdbcType.TEXT

Mybatis表关联多对一

实例:

实体类bean不变

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<resultMap id="weiboMap" type="Weibo">
<result property="id" column="id"/>
<result property="title" column="title"/>
<result property="content" column="content"/>
<association property="user" javaType="User">
<id property="id" column="user_id"/>
<result property="name" column="name"/>
<result property="dept" column="dept"/>
<result property="mobile" column="mobile"/>
</association>
</resultMap>
<select id="getWeiboInfoById" resultMap="weiboMap" parameterType="int">
SELECT u.*,w.*
FROM t_user u
inner join t_weibo w on u.id = w.user_id
WHERE w.id= #{id}
</select>
</mapper>

测试类

  Weibo weibo = sqlSession.selectOne("com.self.dao.UserMapper.getWeiboInfoById", 1000);
System.out.println("---------"+JSON.toJSONString(weibo));

输出:

---------{"content":"史料记载中第一个神圣巨龙使骑士,在所有的文明中,他都是传统骑士准则的典范,正直、善良、忠诚、守信、怜悯等一切人类美德的拥有者。人龙神三人组合中的一人。在神界受到龙神以黄金凤的胁迫,毅然解除与冰系神圣巨龙使泰穆格尔赛的龙骑士契约,解除龙神的威胁,后与光明神同归于尽。","id":2,"title":"大青山描述","user":{"dept":"amy empire","id":2,"name":"大青山"}}

Mybatis 多对多

mybatis3.0 添加了association和collection标签专门用于对多个相关实体类数据进行级联查询,但仍不支持多个相关实体类数据的级联保存和级联删除操作。因此在进行实体类多对多映射表设计时,需要专门建立一个关联对象类对相关实体类的关联关系进行描述(映射表 user_side )。

多对多关系本质上还是一对多关系的延伸,各自是一对多关系的联系,因此变成了多对多的关系,在对单个数据处理时还是一对多关系的处理。

前提准备:

CREATE TABLE `t_side` (
`side_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`side_name` varchar(254) NOT NULL COMMENT '势力名称',
`create_emp` bigint(20) NOT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`modify_emp` bigint(20) DEFAULT NULL COMMENT '修改人',
`modify_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`side_id`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8 COMMENT='所属势力表'; INSERT INTO `hello_mybatis`.`t_side` ( `side_name`, `create_emp`, `create_time`, `modify_emp`, `modify_time`) VALUES
('艾米帝国', '10', '2020-04-01 16:36:25', '10', '2020-04-01 16:36:28'); INSERT INTO `hello_mybatis`.`t_side` ( `side_name`, `create_emp`, `create_time`, `modify_emp`, `modify_time`) VALUES
('哈米尔王国', '10', '2020-04-01 16:36:25', '10', '2020-04-01 16:36:28'); INSERT INTO `hello_mybatis`.`t_side` ( `side_name`, `create_emp`, `create_time`, `modify_emp`, `modify_time`) VALUES
('恶魔岛', '10', '2020-04-01 16:36:25', '10', '2020-04-01 16:36:28'); INSERT INTO `hello_mybatis`.`t_side` ( `side_name`, `create_emp`, `create_time`, `modify_emp`, `modify_time`) VALUES
('精灵王国', '10', '2020-04-01 16:36:25', '10', '2020-04-01 16:36:28'); CREATE TABLE `t_user_side` (
`user_id` int(10) unsigned NOT NULL COMMENT '用户id' ,
`side_id` int(10) unsigned NOT NULL COMMENT '所属势力id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT ='用户势力映射表'; INSERT INTO `hello_mybatis`.`t_user_side` (`user_id`, `side_id`) VALUES ('1', '100');
INSERT INTO `hello_mybatis`.`t_user_side` (`user_id`, `side_id`) VALUES ('1', '101');
INSERT INTO `hello_mybatis`.`t_user_side` (`user_id`, `side_id`) VALUES ('1', '103');
INSERT INTO `hello_mybatis`.`t_user_side` (`user_id`, `side_id`) VALUES ('3', '100');
INSERT INTO `hello_mybatis`.`t_user_side` (`user_id`, `side_id`) VALUES ('2', '100'); sql错误修改 ALTER TABLE t_user_group RENAME t_user_side; ALTER TABLE t_user_side CHANGE group_id side_id int(10) unsigned NOT NULL COMMENT '所属势力id';

实例:

bean对象

public class User {

    /**
* 成员所属势力群
*/
private List<Side> sides;
/**
* id
*/
private int id;
/**
* 名字
*/
private String name;
/**
* 部门,帝国
*/
private String dept;
/**
* 联系号码
*/
private String phone;
/**
* 身高
*/
private BigDecimal height; //private List<Weibo> weibos;
/**
* 创建人
*/
private Long createEmp;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改人
*/
private Long modifyEmp;
/**
* 修改时间
*/
private Date modifyTime;
} public class Side { /**
* 所属势力下成员
*/
private List<User> users;
/**
* 所属势力id
*/
private int sideId;
/**
* 所属势力名称
*/
private String sideName;
/**
* 创建人
*/
private Long createEmp;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改人
*/
private Long modifyEmp;
/**
* 修改时间
*/
private Date modifyTime;
} public class UserSide { /**
* 用户id
*/
private int userId; /**
* 势力id
*/
private int sideId;
}

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="User" type="com.self.bean.User" />
<typeAlias type="com.self.bean.Side" alias="Side"/>
<typeAlias type="com.self.bean.UserSide" alias="UserSide"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/hello_mybatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments> <mappers>
<mapper resource="com/self/dao/UserMapper.xml" />
<mapper resource="com/self/dao/SideMapper.xml"/>
<mapper resource="com/self/dao/UserSideMapper.xml"/>
</mappers>
</configuration>

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.self.dao.UserMapper">
<resultMap id="userMap" type="User">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="phone" column="phone"/>
<collection property="sides" column="user_id" select="com.self.dao.UserSideMapper.getSidesByUserId"/>
</resultMap>
<select id="getUserById" parameterType="int" resultMap="userMap">
select * from `t_user` where id = #{id}
</select>
</mapper>

SideMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.self.dao.SideMapper">
<resultMap id="sideMap" type="Side">
<result property="sideId" column="side_id"/>
<result property="sideName" column="side_name"/>
<collection property="users" column="side_id" select="com.self.dao.UserSideMapper.getUsersBySideId"/>
</resultMap> <select id="getSideById" parameterType="int" resultMap="sideMap">
select * from `t_side` where side_id = #{sideId}
</select> <insert id="insert" parameterType="Side">
insert into t_side(`side_name`, `create_emp`, `create_time`, `modify_emp`, `modify_time`) VALUES
(#{sideName},#{createEmp},#{createTime},#{modifyEmp},#{modifyTime})
</insert>
</mapper>

UserSideMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.self.dao.UserSideMapper">
<resultMap id="userSideMap" type="UserSide">
<result property="userId" column="user_id"/>
<result property="sideId" column="side_id"/>
</resultMap>
<resultMap id="userMap" type="User">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="phone" column="phone"/>
</resultMap>
<resultMap id="sideMap" type="Side">
<result property="sideId" column="side_id"/>
<result property="sideName" column="side_name"/>
<collection property="users" column="side_id" select="com.self.dao.UserSideMapper.getUsersBySideId"/>
</resultMap>
<select id="getSidesByUserId" resultMap="sideMap" parameterType="int">
select s.* , us.user_id from t_side s inner join t_user_side us on s.side_id = us.side_id
where us.user_id = #{userId}
</select> <select id="getUsersBySideId" resultMap="userMap" parameterType="int">
select u.* , us.side_id from t_user u inner join t_user_side us on u.id = us.user_id
where us.side_id = #{sideId}
</select> <insert id="insert" parameterType="UserSide">
INSERT INTO t_user_side (`user_id`, `group_id`) VALUES (#{userId},#{sideId})
</insert>
</mapper> //测试类代码
UserSideMapper us = sqlSession.getMapper(UserSideMapper.class);
List<Side> sides = us.getSidesByUserId(1);
StringBuilder sb = new StringBuilder();
for (Side side : sides) {
sb.append(side.getSideName()+", ");
}
System.out.println("----------该成员所属势力-------------"+sb);
List<User> users = us.getUsersBySideId(100);
StringBuilder sb2 = new StringBuilder();
for (User user : users) {
sb2.append(user.getName() + ", ");
}
System.out.println("----------该势力拥有的成员-------------"+sb2);

输出:

----------该成员所属势力-------------艾米帝国,  哈米尔王国,  精灵王国,
----------该势力拥有的成员-------------艾米, 池寒枫, 大青山,

报错:

1、Cannot find class: userMap 原因:

Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'com/self/dao/UserMapper.xml'. Cause: org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'userMap'.  Cause: java.lang.ClassNotFoundException: Cannot find class: userMap

  <select id="getUserById" parameterType="int" resultMap="userMap">
select * from `t_user` where id = #{id}
</select>
<!-- 自定义的bean实例类型要指定resultMap,resultType普通bean用的-->
<select id="getUserById" parameterType="int" resultType="userMap">
select * from `t_user` where id = #{id}
</select>

Mybatis与Spring集成

框架搭建的核心就是配置文件。

实例:

pom.xml依赖

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--=================mybatis框架依赖===============-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.31</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<!--=================mybatis框架依赖===============-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
<!--=================mybatis整合spring依赖===============-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--=================mybatis整合spring依赖===============-->
<!--=================spring依赖===============-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>3.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.0.9.RELEASE</version>
</dependency>
<!--=================spring依赖===============--> <!--=================切面依赖===============-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<!--=================切面依赖===============-->
</dependencies>

web.xml 配置spring 和springmvc容器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>hellomybatis</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
WEB-INF/hellomybatis-servlet.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>hellomybatis</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext.xml</param-value>
</context-param> <listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> </web-app>

hellomybatis-servlet.xml springmvc配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd"> <!--spring mvc容器扫描配置-->
<context:component-scan base-package="com.self" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

applicationContext.xml spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd">
<!--spring 容器扫描配置-->
<context:component-scan base-package="com.self">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/hello_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean> <!-- 添加事务支持 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 注册事务管理驱动 表示支持声明式事务 @Transactional 注解标注的会被代理实现事务,但要用在有接口的public方法中-->
<!--基于注解的方式使用事务配置声明-->
<tx:annotation-driven transaction-manager="transactionManager" /> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--dataSource连接池-->
<property name="dataSource" ref="dataSource"/>
<!--configLocation属性指定mybatis的核心配置文件-->
<!--如果想把下面整个mybatis-config.xml文件property注释删除了,那么在 UserMapper.xml 这些文件里的type就要配置成全路径的格式如com.self.bean.Side
否则会找不到bean-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 所有配置的mapper文件 该配置相当于是mybatis-config.xml里的mappers配置,在这边直接扫描获取了-->
<property name="mapperLocations" value="classpath*:com/self/dao/*.xml"/>
<!--typeAliasesPackage:批量别名处理 通过这些property就可以把mybatis-config.xml替代掉了-->
<property name="typeAliasesPackage" value="com.self.bean"/>
</bean>
<!-- Mapper代理开发,使用Spring自动扫描MyBatis的接口并装配 (Spring将指定包中的所有被@Mapper注解标注的接口自动装配为MyBatis的映射接口) -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- mybatis-spring组件的扫描器(UserMapper.java),com.dao只需要接口(接口方法与SQL映射文件中的相同) -->
<property name="basePackage" value="com.self.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>

mybatis-config.xml mybaits配置文件可以不写了

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--mybatis-config.xml可以不用配置,直接通过扫描来配置mapper-->
<!-- <typeAliases>
<typeAlias alias="User" type="com.self.bean.User" />
<typeAlias type="com.self.bean.Side" alias="Side"/>
<typeAlias type="com.self.bean.UserSide" alias="UserSide"/>
<typeAlias type="com.self.bean.Weibo" alias="Weibo"/>
</typeAliases>-->
<!--
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/hello_mybatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
-->
<!-- <mappers>
<mapper resource="com/self/dao/UserMapper.xml" />
<mapper resource="com/self/dao/SideMapper.xml"/>
<mapper resource="com/self/dao/UserSideMapper.xml"/>
</mappers>-->
</configuration>

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<select id="getUsers" resultType="User">
select * from `t_user` where 1 = 1
</select>
</mapper> @Repository
//@Mapper
public interface UserMapper {
public List<User> getUsers();
} public interface UserService {
public List<User> getUsers();
} @Service("userService")
public class UserServiceImpl implements UserService {
@Override
public List<User> getUsers() {
return userMapper.getUsers();
}
} @Controller
public class UserController { @Autowired
private UserService userService; @RequestMapping("/user")
public void getUsers(){
System.out.println(JSON.toJSONString(userService.getUsers()));
} }

输出:

[{"dept":"amy empire","height":1.75,"id":1,"name":"艾米","phone":"18956563228"},{"dept":"amy empire","height":1.85,"id":2,"name":"大青山","phone":"18956563229"},{"dept":"amy empire","height":1.83,"id":3,"name":"池寒枫","phone":"22056545"},{"dept":"森林矮人王国","height":1.56,"id":4,"name":"霍恩斯","phone":"852-253521"}]

报错:

1、@RequestMapping("/user") 注解用不了。

<!--=================需要依赖 spring-web===============-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.0.9.RELEASE</version>
</dependency>

2、import不了DispatcherServlet

<!--=================需要依赖 spring-webmvc===============-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.0.9.RELEASE</version>
</dependency>

3、No bean named 'userController' available,找不到bean。报这个错是因为使用容器扫描配置时注释了过滤的Controller类型,但是没有把use-default-filters="false" 配置删了,倒置没有过滤的Controller配置的话,那么默认是全部过滤,就没有bean实例被扫描进去了。要改成下面这样。

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'userController' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:687)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1207) <!--spring 容器扫描配置 错误配置-->
<context:component-scan base-package="com.self" use-default-filters="false">
<!--<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->
</context:component-scan> <!--spring 容器扫描配置 正确配置-->
<context:component-scan base-package="com.self" >
</context:component-scan>

context:component-scan除了具有context:annotation-config的功能之外,context:component-scan还可以在指定的package下扫描以及注册javabean 。还具有自动将带有@component,@service,@Repository等注解的对象注册到spring容器中的功能。

因此当使用 context:component-scan 后,就可以将 context:annotation-config移除。

4、Result Maps collection already contains value for com.self.dao.SideMapper.sideMap .集合容器里已经有该类型的值了。这个报错是因为当配置了mybatis-config.xml文件的mappers的同时还注册了MapperScannerConfigurer 的自动扫描MyBatis的接口并装配。注册了两遍。因此只要配置一个就行了,我这边是把mybatis-config.xml文件注释掉了。

Caused by: org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'file [F:\self\hellomybatis\target\classes\com\self\dao\SideMapper.xml]'. Cause: java.lang.IllegalArgumentException: Result Maps collection already contains value for com.self.dao.SideMapper.sideMap

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--mybatis-config.xml可以不用配置,直接通过扫描来配置mapper-->
<!-- <typeAliases>
<typeAlias alias="User" type="com.self.bean.User" />
<typeAlias type="com.self.bean.Side" alias="Side"/>
<typeAlias type="com.self.bean.UserSide" alias="UserSide"/>
<typeAlias type="com.self.bean.Weibo" alias="Weibo"/>
</typeAliases>-->
<!--
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/hello_mybatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
-->
<!-- <mappers>
<mapper resource="com/self/dao/UserMapper.xml" />
<mapper resource="com/self/dao/SideMapper.xml"/>
<mapper resource="com/self/dao/UserSideMapper.xml"/>
</mappers>-->
</configuration>

5、Error creating bean with name 'userController': Unsatisfied dependency expressed through field 'userService'。注册userControllerbean时依赖的userService不能得到的注入。

这个原因还是出在spring 容器扫描配置配置上,use-default-filters="false" 使用不当。

 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userController': Unsatisfied dependency expressed through field 'userService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.self.service.UserService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

    <!--spring 容器扫描配置-->
<context:component-scan base-package="com.self" use-default-filters="false">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan> <!--spring 容器扫描配置-->
<context:component-scan base-package="com.self">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

use-default-filters 属性的默认值为 true,即使用默认的 Filter 进行包扫描,而默认的 Filter 对标有 @Service,@Controller和@Repository 的注解的类进行扫描。设置为 false,即不使用默认的 Filter 进行扫描。也就是不扫描,只扫描下面配置的include-filter,下面配置的扫描注解有几个就只扫几个,有一个就扫一个。

我们希望 SpringMVC容器 只来控制网站的跳转逻辑,所以我们只希望 SpringMVC 的配置扫描 @Controllerce 注解标注的类,不希望它扫描其余注解标注的类,所以设置了 use-default-filters 为 false,并使用 context:include-filter 子标签设置其只扫描带有 @Controller 注解标注的类。

Spring 容器就不同了,我们希望 Spring 只不扫描带有 @Controller 注解标注的类,而扫描其他注解标注的类,而这时建立在使用默认的 Filter 进行扫描的基础上,设置了 context:exclude-filter 标签,不扫描 @Controller 注解标注的类,所以不应该设置 use-default-filters 为 false 。

use-default-filters="false" 需要和 context:include-filter 一起使用,而不能和 context:exclude-filter 属性一起使用。

参考

MyBatis分页

分页可以分为逻辑分页和物理分页。

逻辑分页是我们的程序在显示每页的数据时,首先查询得到表中的1000条数据,然后根据当前页的“页码”选出其中的100条数据来显示。 物理分页是程序先判断出该选出这1000条的第几条到第几条,然后数据库根据程序给出的信息查询出程序需要的100条返回给我们的程序。

分页待学习其他的方法,这个不好。

MyBatis动态SQL语句

mybatis 的动态sql语句是基于OGNL表达式的。可以方便的在 sql 语句中实现某些逻辑. 总体说来mybatis 动态SQL 语句主要有以下几类:

  1. if 语句 (简单的条件判断)

  2. choose (when,otherwize) ,相当于java 语言中的 switch ,与 jstl 中的choose 很类似.

  3. trim (对包含的内容加上 prefix,或者 suffix 等,前缀,后缀)

  4. where (主要是用来简化sql语句中where条件判断的,能智能的处理 and or ,不必担心多余导致语法错误)

  5. set (主要用于更新时)

  6. foreach (在实现 mybatis in 语句查询时特别有用)

    <select id="getUsersByCond" resultType="User" parameterType="User">
    select * from `t_user` where 1 = 1
    <if test="name != null">
    and name = #{name}
    </if>
    <if test="phone != null">
    and phone = #{phone}
    </if>
    <if test="author != null and author.name != null">
    AND author_name like #{author.name}
    </if>
    </select>

    select * from `t_user` where 1 = 1

    AND name like #{name}

    and phone = #{phone}

    and dept = #{dept}

     <select id="getUsersByCondition" resultType="User" parameterType="User">
    select * from `t_user`
    <where>
    <if test="name != null">
    and name = #{name}
    </if>
    <if test="phone != null">
    and phone = #{phone}
    </if>
    <if test="dept != null">
    and dept = #{dept}
    </if>
    </where>
    </select>

     <select id="getUsersByCondition" resultType="User" parameterType="User">
    select * from `t_user`
    <trim prefix="where" prefixOverrides="and|or">
    <if test="name != null">
    and name = #{name}
    </if>
    <if test="phone != null">
    and phone = #{phone}
    </if>
    <if test="dept != null">
    and dept = #{dept}
    </if>
    </trim>
    </select>

    <update id="update" parameterType="User">
    update t_user
    <set>
    <if test="name != null">
    name = #{name},
    </if>
    <if test="dept != null">
    dept = #{dept},
    </if>
    <if test="phone != null">
    phone = #{phone},
    </if>
    <if test="height != null">
    height = #{height},
    </if>
    <if test="modifyEmp != null">
    modify_emp = #{modifyEmp},
    </if>
    <if test="modifyTime != null">
    modify_time = #{modifyTime},
    </if>
    </set>
    where id = #{id}
    </update>

    update t_user

    name = #{name},

    dept = #{dept},

    phone = #{phone},

    height = #{height},

    modify_emp = #{modifyEmp},

    modify_time = #{modifyTime},

    where id = #{id}

    select * from `t_user`

    and name = #{name}

    and phone = #{phone}

    and dept = #{dept}

    and id in

    #{id}

     <select id="getUsersByCondition" resultType="User" parameterType="User">
    <bind name="deptBlur" value="'%' + dept + '%'"></bind>
    select * from `t_user`
    <where>
    <if test="name != null">
    and name = #{name}
    </if>
    <if test="phone != null">
    and phone = #{phone}
    </if>
    <if test="dept != null">
    and dept like #{deptBlur}
    </if>
    <if test="ids != null">
    and id in
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
    #{id}
    </foreach>
    </if>
    </where>
    </select>

    insert id="insert">





    select seq_users.nextval from dual





    select nextval for seq_users from sysibm.sysdummy1"





    insert into users values (#{id}, #{name})

prefixOverrides使用参考

foreach查询的写法

public class User {

    /**
* 成员所属势力群
*/
private List<Side> sides;
/**
* id
*/
private int id; private List<Integer> ids; private Integer[] idsArr;
/**
* 名字
*/
private String name;
/**
* 部门,帝国
*/
private String dept;
/**
* 联系号码
*/
private String phone;
/**
* 身高
*/
private BigDecimal height; //private List<Weibo> weibos;
/**
* 创建人
*/
private Long createEmp;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改人
*/
private Long modifyEmp;
/**
* 修改时间
*/
private Date modifyTime; public Integer[] getIdsArr() {
return idsArr;
} public void setIdsArr(Integer[] idsArr) {
this.idsArr = idsArr;
} public List<Integer> getIds() {
return ids;
} public void setIds(List<Integer> ids) {
this.ids = ids;
}
}

测试类:

 User user = new User();
Integer[] idsArr = {1,2,3,4};
user.setIdsArr(idsArr);
user.setDept("amy empire");
uc.getUserCondition(user);
//===============================================
User user = new User();
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
user.setIds(ids);
user.setDept("amy empire");
uc.getUserCondition(user);

mybaits if标签语句

实例:

    <select id="getUsersByCondition" resultType="User" parameterType="User">
select * from `t_user`
<where>
<if test="name != null and name !='' ">
and name = #{name,jdbcType=VARCHAR}
</if>
<if test="phone != null and phone !='' ">
and phone = #{phone,jdbcType=VARCHAR}
</if>
<if test="dept != null and dept != '' ">
<!-- 另外一种模糊查询%的拼接方法 -->
and dept like CONCAT(CONCAT('%', #{dept,jdbcType=VARCHAR}),'%')
</if>
<if test="ids != null">
and id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</if>
</where>
</select>

错误:

里的连接词 and 不能大写,否则会识别不了,要用小写的and。因为mybatis区分大小写,and是约定的关键字,应该用小写的and。

Exception in thread "main" org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Error evaluating expression 'name != null AND name !='' '. Cause: org.apache.ibatis.ognl.ExpressionSyntaxException: Malformed OGNL expression: name != null AND name !=''  [org.apache.ibatis.ognl.ParseException: Encountered " <IDENT> "AND "" at line 1, column 14.
Was expecting one of: <if test="phone != null and phone !='' ">
AND phone = #{phone,jdbcType=VARCHAR}
</if>
<!-- 下面才是正确写法 -->
<if test="phone != null and phone !='' ">
and phone = #{phone,jdbcType=VARCHAR}
</if>

!和= 之间多了一个空格也会报错,if test="phone ! = null and phone ! ='' 。应该去掉空格

<!-- 下面才是正确写法 -->
<if test="phone != null and phone !='' ">
and phone = #{phone}
</if>

MyBatis where标签语句

“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以 AND 或OR 开头的,则它会剔除掉。

Mybatis set标签

当 update 语句中没有使用 if 标签时,如果有一个参数为 null,都会导致错误。

当在 update 语句中使用if标签时,如果前面的if没有执行,则或导致逗号多余错误。使用set标签可以将动态的配置 SET 关键字,并剔除追加到条件末尾的任何不相关的逗号。使用 if+set 标签修改后,如果某项为 null 则不进行更新,而是保持数据库原值。

报错:如果 set 包含的内容为空的话则会出错 ,如下。

update t_user                    where id = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where id = 2' at line 3
;

Mybatis trim标签

trim 是更灵活用来去处多余关键字的标签,它可以用来实现 where 和 set 的效果。trim 元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix;可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides;正因为 trim 有这样的功能,所以我们也可以非常简单的利用 trim 来代替 where 元素的功能。

choose (when, otherwise)标签

有时候我们并不想应用所有的条件,而只是想从多个选项中选择一个。而使用if标签时,只要test中的表达式为 true,就会执行 if 标签中的条件。MyBatis 提供了 choose 元素。if标签是与(and)的关系,而 choose 是或(or)的关系。

choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql。类似于Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。

MyBatis SqlSessionDaoSupport实例

待验证。没用

MyBatis打印输出SQL语句

实例:

log4j.properties

log4j.rootLogger=debug,stdout,logfile
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=C:/mybatis_show_sql.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n log4j.logger.com.ibatis=DEBUG
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

pom.xml依赖

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>

输出:

DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@647f63b4] was not registered for synchronization because synchronization is not active
DEBUG - Fetching JDBC Connection from DataSource
DEBUG - Creating new JDBC DriverManager Connection to [jdbc:mysql://localhost:3306/hello_mybatis]
DEBUG - JDBC Connection [com.mysql.jdbc.JDBC4Connection@481b3df0] will not be managed by Spring
DEBUG - ==> Preparing: select * from `t_user` WHERE id in ( ? , ? , ? )
DEBUG - ==> Parameters: 1(Integer), 2(Integer), 3(Integer)
DEBUG - <== Total: 3
DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@647f63b4]
DEBUG - Returning JDBC Connection to DataSource
ERROR - [{"dept":"amy empire","height":1.75,"id":1,"name":"大青山","phone":"18956563228"},{"dept":"amy empire","height":1.85,"id":2,"name":"艾米哈珀","phone":"18956563228"},{"dept":"amy empire","height":1.83,"id":3,"name":"池寒枫","phone":"22056545"}]

疑问:

Q:什么是OGNL表达式?

A:对象导航图语言(Object Graph Navigation Language),简称OGNL,是应用于Java中的一个开源的表达式语言(Expression Language),它被集成在mybatis等框架中,作用是对数据进行访问,它拥有类型转换、访问对象方法、操作集合对象等功能。

Q:像下面这种变量要怎么配置各个环境的变量,方便替换,怎么读取到相应的properties文件?value="${driver}"

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>

Q:MyBatis ofType和javaType区别?

A:JavaType和ofType都是用来指定对象类型的,但是JavaType是用来指定pojo中属性的类型,而ofType指定的是映射到list集合属性中pojo的类型。

Q:下面两个resultMap有什么区别么?为什么要多指导jdbcType 和javaType,去掉这两个后运行程序还是正常,那这两个有什么作用?

    <resultMap id="userMap" type="User">
<result property="id" column="user_id"/>
<result property="name" column="name"/>
<result property="dept" column="dept"/>
<result property="phone" column="phone"/>
<collection property="weibos" ofType="com.self.bean.Weibo" column="user_id">
<!--<id property="id" column="t_weibo.id" javaType="int" jdbcType="INTEGER" />-->
<!--不加表别名或者表名来区分id的话会导致查询不出多条数据-->
<id property="id" column="p.id" />
<result property="title" column="title" />
<result property="content" column="content" />
</collection>
</resultMap>
<!--===========================分割===========================-->
<resultMap id="userMap" type="User">
<result property="id" column="user_id"/>
<result property="name" column="name"/>
<result property="dept" column="dept"/>
<result property="phone" column="phone"/>
<collection property="weibos" ofType="com.self.bean.Weibo" column="user_id">
<!--<id property="id" column="t_weibo.id" javaType="int" jdbcType="INTEGER" />-->
<!--不加表别名或者表名来区分id的话会导致查询不出多条数据-->
<id property="id" column="p.id" javaType="int" jdbcType="INTEGER" />
<result property="title" column="title" javaType="string" jdbcType="VARCHAR"/>
<result property="content" column="content" javaType="string" jdbcType="VARCHAR"/>
</collection>
</resultMap>

Q:@Mapper注解的作用是什么?

@Mapper
public interface UserMapper {}

Q:在写mybatissql时为什么查询绑定的值还要指定上jdbcType类型呢?不写也是可以的,写和不写的区别?如下

and phone = #{phone,jdbcType=VARCHAR}

Q:有空研究下log4j.properties上的具体配置如何操作,自定义,形成文档。

MyBatis 教程 ——检视阅读的更多相关文章

  1. AJAX教程——检视阅读

    AJAX教程--检视阅读 参考 AJAX 教程--菜鸟 AJAX 教程--w3cschool AJAX 教程--w3school.cn AJAX 教程--易百 AJAX = Asynchronous ...

  2. Spring MVC教程——检视阅读

    Spring MVC教程--检视阅读 参考 Spring MVC教程--一点--蓝本 Spring MVC教程--c语言中午网--3.0版本太老了 Spring MVC教程--易百--4.0版本不是通 ...

  3. SpringBoot教程——检视阅读

    SpringBoot教程--检视阅读 参考 SpringBoot教程--一点--蓝本--springboot2.1.1 SpringBoot教程--易百--springboo2.0.5.RELEASE ...

  4. Spring教程检视阅读

    Spring教程检视阅读 地址 可供参考的教程 <菜鸟学 SSH> <Spring Boot 那些事> <初识 Spring Security> <Sprin ...

  5. MySQL 教程--检视阅读

    MySQL 教程--检视阅读 准备:Windows 上安装 MySQL 教程地址,PHP语言基础 教程地址2 教程地址3,有讲数据库的备份和恢复 教程地址4,w3c.china,php基础,扩展阅读 ...

  6. NIO教程 ——检视阅读

    NIO教程 --检视阅读 参考 BIO,NIO,AIO 总结 Java NIO浅析 Java NIO 教程--极客,蓝本 Java NIO 系列教程 --并发编程网 BIO,NIO--知乎 NIO 入 ...

  7. JDBC教程——检视阅读

    JDBC教程--检视阅读 参考 JDBC教程--W3Cschool JDBC教程--一点教程,有高级部分 JDBC教程--易百 JDBC入门教程 – 终极指南 略读 三层架构详解,JDBC在数据访问层 ...

  8. Java Web教程——检视阅读

    Java Web教程--检视阅读 参考 java web入门--概念理解.名词解释 Java Web 教程--w3school 蓝本 JavaWeb学习总结(一)--JavaWeb开发入门 小猴子mo ...

  9. Servlet 教程——检视阅读

    Servlet 教程--检视阅读 参考 Servlet教程--菜鸟--蓝本 Servlet教程--w3cschool Servlet教程--易百 servlet依赖maven依赖: <!--se ...

随机推荐

  1. 计算机思维的逻辑基础是什么? & 计算思维

    l  计算机思维的逻辑基础: 计算机思维是指人们操作计算机时,计算机行使特定功能的运作方式. 逻辑基础则是指支撑事物运作的基本法则. 因而,计算机思维的逻辑基础可以理解为,计算机在行使特定功能时,其运 ...

  2. Spring Cloud Gateway 实现Token校验

    在我看来,在某些场景下,网关就像是一个公共方法,把项目中的都要用到的一些功能提出来,抽象成一个服务.比如,我们可以在业务网关上做日志收集.Token校验等等,当然这么理解很狭隘,因为网关的能力远不止如 ...

  3. 一起了解 .Net Foundation 项目 No.15

    .Net 基金会中包含有很多优秀的项目,今天就和笔者一起了解一下其中的一些优秀作品吧. 中文介绍 中文介绍内容翻译自英文介绍,主要采用意译.如与原文存在出入,请以原文为准. NUnit Test Fr ...

  4. 字符串、bute[]数组和十六进制字符串的相互转换

    1.字符串转换成十六进制字符串 public static String str2HexStr(String str) { if (EncodingUtil.isEmpty(str)) { retur ...

  5. 3DGIS与BIM集成集成技术及铁路桥梁可视化系统

    3DGIS与BIM的集成技术 3DGIS与BIM的集成技术包括2部分:一是将Revit软件生成的BIM针对3DGIS的快速无损格式转换,这种转换包括几何信息(如形状.位置等信息)和属性信息(如建筑信息 ...

  6. 内网渗透之权限维持 - MSF

    年初九 天公生 0x034 MSF(美少妇) 启动msf msfconsole 先启动msf依赖的postgresql数据库 初始化数据库 msfdb init (要用普通用户) msf路径 /usr ...

  7. model进阶

    本节目录 一 QuerySet 二 中介模型 三 查询优化 四 extra 五 整体插入 一 QuerySet 可切片 使用Python 的切片语法来限制查询集记录的数目 .它等同于SQL 的LIMI ...

  8. 探究Spring Boot中的接收参数问题与客户端发送请求传递数据

    结合此篇参考Spring框架学习笔记(9)--API接口设计相关知识及具体编码实现 在使用Spring Boot进行接收参数的时候,发现了许多问题,之前一直都很忙,最近才稍微有空研究一下此问题. 网上 ...

  9. 03 Uipath调用VBA脚本,处理excel文档格式

    前言: 在平时我们的工作中,经常需要使用Uipath自动的导入大量数据到Excel表格中,但是却发现,数据导入到Excel之后,格式却是很乱,基本不能看,就像下图: ​ 而Uipath对Excel的操 ...

  10. 使用 Github Action 进行前端自动化发布

    前言 说起自动化,无论是在公司还是我们个人的项目中,都会用到或者编写一些工具来帮助我们去处理琐碎重复的工作,以节约时间提升效率,尤其是我们做前端开发会涉及诸如构建.部署.单元测试等这些开发工作流中重复 ...