01.MyBatis3入门

MyBatis 入门

什么是 MyBatis?

为什么需要 MyBatis?

MyBatis 的优点:

MyBatis HelloWorld

CREATE DATABASE `mybatis`; -- 创建数据库“mybatis”;

USE `mybatis`; -- 使用`mybatis`;

DROP TABLE IF EXISTS `user`; -- 如果存在则删除表`用户`;

CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 创建表`用户`(
-- `id` int ( 20 ) NOT NULL ,
-- `name` varchar ( 30 )默认为NULL ,
-- `pwd` varchar ( 30 )默认为NULL ,
-- 主键(`id`)
-- ) 引擎= InnoDB默认字符集= utf8;


insert  into `user`(`id`,`name`,`pwd`) values (1,'hacket','123456'),(2,'张三','abcdef'),(3,'李四','987654');
-- 插入` user `(`id`,`name`,`pwd`)值( 1 , 'hacket' , '123456' ) , ( 2 , '张三' , 'abcdef' ),( 3 , '李四' , '987654' );
<dependencies>
	<!--junit module-->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<scope>test</scope>
		<version>4.13.1</version>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.47</version>
	</dependency>
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis</artifactId>
		<version>3.5.16</version>
	</dependency>
</dependencies>
<?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="com.mysql.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="zfs1314520"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/userMapper.xml"/>
    </mappers>
</configuration>
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory = null;
    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();
    }
}
public class User {
    private int id;  //id
    private String name;   //姓名
    private String pwd;   //密码
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
public interface UserMapper {
    //查询所有用户
    List<User> selectUser();
}
<?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="me.hacket.mapper.UserMapper">
    <select id="selectUser" resultType="me.hacket.model.User">
        select *
        from user
    </select>
</mapper>

注意: namespace 不能写错,值为 UserMapper 类的全路径

public class MyTest {
    @Test
    public void selectUser() {
        SqlSession session = MybatisUtils.getSession();
        //方法一:
        //List<User> users = session.selectList("com.kuang.mapper.UserMapper.selectUser");
        //方法二:
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.selectUser();
        for (User user : users) {
            System.out.println(user);
        }
        session.close();
    }
}

namespace

  1. 将上面案例中的 UserMapper 接口改名为 UserDao;
  2. 将 userMapper.xml 中的 namespace 改为为 UserDao 的路径 .
  3. 再次测试

配置文件中 namespace 中的名称为对应 Mapper 接口或者 Dao 接口的完整包名,必须一致

MyBatis CRUD XML

select

select 标签

单个参数

根据 id 查询用户

public interface UserMapper {
   //根据id查询用户
   User selectUserById(int id);
}
<?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="me.hacket.mapper.UserMapper">
    <select id="selectUserById" resultType="me.hacket.model.User">
        select *
        from user
        where id = #{id}
    </select>
</mapper>
public interface UserMapper {
	// 根据id查询用户
    User selectUserById(@Param("id") int id2);
}

多个参数

根据 密码 和 名字 查询用户

方式一:@Param

  1. 在接口方法的参数前加 @Param 属性
  2. Sql 语句编写的时候,直接取 @Param 中设置的值即可,不需要单独设置参数类型
public interface UserMapper {
	// 2个参数:通过用户名和密码查询用户
	// username和pwd是传入的参数,@Param("username")和@Param("pwd")是给参数起别名,方便在mapper.xml中占位
	// 如 select * from user where name = #{username} and pwd = #{pwd}
	User selectUserByNameAndPassword(@Param("username") String username, @Param("pwd") String pwd);
}
<select id="selectUserByNameAndPassword" resultType="me.hacket.model.User">
	select *
	from user
	where name = #{username}
	  and pwd = #{pwd}
</select>

方式二:Map

public interface UserMapper {
	 // 多个参数:用Map
	User selectUserByNameAndPassword2(@Param("params") Map<String, Object> map);
}
<?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="me.hacket.mapper.UserMapper">
    <select id="selectUserByNameAndPassword2" parameterType="mAP" resultType="me.hacket.model.User">
        select *
        from user
        where name = #{params.username}
          and pwd = #{params.pwd}
    </select>
</mapper>
@Test
public void testSelectUserByNameAndPassword2() {
	SqlSession session = MybatisUtils.getSession();
	UserMapper mapper = session.getMapper(UserMapper.class);
	Map<String, Object> map = new HashMap<>();
	map.put("username", "hacket");
	map.put("pwd", "123456");
	User user = mapper.selectUserByNameAndPassword2(map);
	System.out.println(user);
	session.close();
}

总结: 如果参数过多,我们可以考虑直接使用 Map 实现,如果参数比较少,直接传递参数即可

insert

使用 insert 标签进行插入操作,它的配置和 select 标签差不多。

// 添加一个用户
int addUser(User user);
<?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="me.hacket.mapper.UserMapper">
    <insert id="addUser" parameterType="me.hacket.model.User">
        insert into user (id, name, pwd)
        values (#{id}, #{name}, #{pwd})
    </insert>
</mapper>
@Test
public void testAddUser() {
	SqlSession session = MybatisUtils.getSession();
	UserMapper mapper = session.getMapper(UserMapper.class);
	User user = new User(5, "xiaosheng", "zfs123456");
	int i = mapper.addUser(user);
	System.out.println(i);
	session.commit(); //提交事务,重点!不写的话不会提交到数据库
	session.close();
}

注意点:增、删、改操作需要提交事务

update

使用 update 标签进行更新操作,它的配置和 select 标签差不多

int updateUser(User user);
<update id="updateUser" parameterType="me.hacket.model.User">
	update user set name=#{name},pwd=#{pwd} where id = #{id}
</update>
@Test
public void testUpdateUser() {
	SqlSession session = MybatisUtils.getSession();
	UserMapper mapper = session.getMapper(UserMapper.class);
	User user = mapper.selectUserById(1);
	user.setPwd("qwert");
	int i = mapper.updateUser(user);
	System.out.println(i);
	session.commit(); //提交事务,重点!不写的话不会提交到数据库
	session.close();
}

delete

//根据id删除用户
int deleteUser(int id);
<delete id="deleteUser" parameterType="int">  
    delete from user where id = #{id}  
</delete>
@Test
public void testDeleteUser() {
	SqlSession session = MybatisUtils.getSession();
	UserMapper mapper = session.getMapper(UserMapper.class);
	int i = mapper.deleteUser(5);
	System.out.println(i);
	session.commit(); //提交事务,重点!不写的话不会提交到数据库
	session.close();
}

小结

XML 配置解析

核心配置文件

configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
<!-- 注意元素节点的顺序!顺序不对会报错 -->

可以阅读 mybatis-config.xml 上面的 dtd 的头文件

environments

官方文档很详细: mybatis – MyBatis 3 | 配置

<environments default="development">
 <environment id="development">
   <transactionManager type="JDBC">
     <property name="..." value="..."/>
   </transactionManager>
   <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 资源方式

<!-- 使用相对于类路径的资源引用 -->
<mappers>
 <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
 <mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<!--
使用映射器接口实现类的完全限定类名
需要配置文件名称和接口名称一致,并且位于同一目录下
-->
<mappers>
 <mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!--
将包内的映射器接口实现全部注册为映射器
但是需要配置文件名称和接口名称一致,并且位于同一目录下
-->
<mappers>
 <package name="org.mybatis.builder"/>
</mappers>

user-mapper.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="me.hacket.mapper.UserMapper">
    <select id="selectUser" resultType="me.hacket.model.User">
        select * from user
    </select>
</mapper>
  1. namespace 和子元素的 id 联合保证唯一 , 区别不同的 mapper
  2. 绑定 DAO 接口
  3. namespace 命名规则 : 包名 + 类名

properties 优化

数据库这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8
username=root
password=123456
<configuration>
   <!--导入properties文件-->
   <properties resource="db.properties"/>

   <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="mapper/UserMapper.xml"/>
   </mappers>
</configuration>

typeAliases 优化

类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。

<!--配置别名,注意顺序-->
<typeAliases>
   <typeAlias type="me.hacket.model.User" alias="User"/>
</typeAliases>

当这样配置时,User 可以用在任何使用 me.hacket.model.User 的地方。

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
   <package name="me.hacket.model"/>
</typeAliases>

每一个在包 me.hacket.model 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。

若有注解,则别名为其注解值。见下面的例子:

@Alias("user")
public class User {
  // ...
}

其他 XML 配置

settings 设置

设置(settings)相关 => 查看帮助文档

一个配置完整的 settings 元素的示例如下:

<settings>
 <setting name="cacheEnabled" value="true"/>
 <setting name="lazyLoadingEnabled" value="true"/>
 <setting name="multipleResultSetsEnabled" value="true"/>
 <setting name="useColumnLabel" value="true"/>
 <setting name="useGeneratedKeys" value="false"/>
 <setting name="autoMappingBehavior" value="PARTIAL"/>
 <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
 <setting name="defaultExecutorType" value="SIMPLE"/>
 <setting name="defaultStatementTimeout" value="25"/>
 <setting name="defaultFetchSize" value="100"/>
 <setting name="safeRowBoundsEnabled" value="false"/>
 <setting name="mapUnderscoreToCamelCase" value="false"/>
 <setting name="localCacheScope" value="SESSION"/>
 <setting name="jdbcTypeForNull" value="OTHER"/>
 <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

类型处理器

对象工厂

日志

Mybatis 内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:

具体选择哪个日志实现工具由 MyBatis 的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。

标准日志实现

指定 MyBatis 应该使用哪个日志记录实现。如果此设置不存在,则会自动发现日志记录实现

<settings>
       <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
Log4j

@Param

@Param 注解用于给方法参数起一个名字。以下是总结的使用原则:

#和$区别

INSERT INTO user (name) VALUES (#{name});
INSERT INTO user (name) VALUES (?);
INSERT INTO user (name) VALUES ('${name}');
INSERT INTO user (name) VALUES ('kuangshen');

MyBatis 传参方式 XML

单个参数

单个参数的传参比较简单,可以是任意形式的,比如 #{a}#{b} 或者 #{param1}但是为了开发规范,尽量使用和入参时一样

UserInfo selectByUserId(String userId);
<select id="selectByUserId" resultType="cn.cb.demo.domain.UserInfo">
	select * from user_info where user_id=#{userId} and status=1
</select>

多个参数

使用索引【不推荐】

多个参数可以使用类似于索引的方式传值,比如 #{param1} 对应第一个参数,#{param2} 对应第二个参数…….

UserInfo selectByUserIdAndStatus(String userId,Integer status);
<select id="selectByUserIdAndStatus" resultType="cn.cb.demo.domain.UserInfo">
	select * from user_info where user_id=#{param1} and status=#{param2}
</select>

注意:由于开发规范,此种方式不推荐开发中使用

使用 @Param

@Param 这个注解用于指定 key,一旦指定了 key,在 SQL 中即可对应的 key 入参。

UserInfo selectByUserIdAndStatus(@Param("userId") String userId, @Param("status") Integer status);
<select id="selectByUserIdAndStatus" resultType="cn.cb.demo.domain.UserInfo">
	select * from user_info where user_id=#{userId} and status=#{status}
</select>

使用 Map

Mybatis 底层就是将入参转换成 Map,入参传 Map 当然也行,此时 #{key} 中的 key 就对应 Map 中的 key

UserInfo selectByUserIdAndStatusMap(Map<String,Object> map);
<select id="selectByUserIdAndStatusMap" resultType="cn.cb.demo.domain.UserInfo">
	select * from user_info where user_id=#{userId} and status=#{status}
</select>
@Test
void contextLoads() {
	Map<String, Object> map=new HashMap<>();
	map.put("userId", "1222");
	map.put("status", 1);
	UserInfo userInfo = userMapper.selectByUserIdAndStatusMap(map);
	System.out.println(userInfo);
}

POJO【推荐】

多个参数可以使用实体类封装,此时对应的 key 就是属性名称,注意一定要有 get 方法

@Data
public class UserInfoReq {
    private String userId;
    private Integer status;
}
UserInfo selectByEntity(UserInfoReq userInfoReq);
<select id="selectByEntity" parameterType="UserInfo" resultType="UserInfo">
	select * from user_info where user_id=#{userId} and status=#{status}
</select>

List 传参

List 传参也是比较常见的,通常是 SQL 中的 in

List<Blog> queryList( List<String> userIds);
<select id="queryList" parameterType="List" resultType="blog">
	select * from blog where author in
	<foreach collection="list" item="item" open="(" separator="," close=")" >
		#{item}
	</foreach>
</select>

collection 可以为 arg0, collection, list

@Test
public void queryListTest() {
	SqlSession session = MybatisUtils.getSession();
	BlogMapper mapper = session.getMapper(BlogMapper.class);
	List<String> ids = new ArrayList<>();
	ids.add("hacket");
	ids.add("秦疆");
	List<Blog> blogs = mapper.queryList(ids);
	System.out.println(blogs);
	session.close();
}

数组传参

这种方式类似 List 传参,依旧使用 foreach 语法。

List<Blog> queryArray(String[] userIds);
<select id="queryArray"  resultType="blog">
	select * from blog where author in
	<foreach collection="array" item="item" open="(" separator="," close=")" >
		#{item}
	</foreach>
</select>
@Test
public void queryArrayTest() {
	SqlSession session = MybatisUtils.getSession();
	BlogMapper mapper = session.getMapper(BlogMapper.class);
	String[] ids = new String[]{"hacket", "秦疆"};
	List<Blog> blogs = mapper.queryArray(ids);
	System.out.println(blogs);
	session.close();
}

生命周期和作用域

MyBatis 生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的 并发问题

生命周期

SqlSessionFactoryBuilder

SqlSessionFactory

SqlSession

最佳作用域

SqlSessionFactoryBuilder : 它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是 方法作用域(也就是局部方法变量)。

SqlSessionFactory :

SqlSession : SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。所以 SqlSession 的最佳的作用域是请求或方法作用域

流程图

MyBatis 遇到问题

userMapper.xml 找不到

通过 MyBatis 测试达梦数据库过程中,运行测试类的时候,项目报错:java.io.IOException: Could not find resource mapper/userMapper.xml

解决方法 1:

  1. 首先看一下你的 UserMapper.xml 文件是不是放在 src 目录下,如果是那么报错的原因就是:maven 是不会去编译 src 目录下的 xml 文件
  2. 需要在项目的 pom.xml 文件中添加这一段代码即可。
<!--在build中配置resouces,来防止资源导出失败问题-->
<build>
	<resources>
		<resource>
			<directory>src/main/resources</directory>
			<includes>
				<include>**/*.properties</include>
				<include>**/*.xml</include>
			</includes>
			<filtering>true</filtering>
		</resource>
		<resource>
			<directory>src/main/java</directory>
			<includes>
				<include>**/*.properties</include>
				<include>**/*.xml</include>
			</includes>
			<filtering>true</filtering>
		</resource>
	</resources>
</build>

解决方法 2:userMapper.xml 放到 resource目录
|300
mybatic-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>
    <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/mybatis?useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="xxx"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/userMapper.xml"/>
    </mappers>
</configuration>

Ref