Mybatis-CRUD

1.1 项目环境

SQL数据

-- 创建数据库
create database mybatis
-- 创建user表
create table user (
  id int primary key auto_increment,
  username varchar(20) not null,
  birthday date,
  sex char(1) default '男',
  address varchar(50)
);
-- 插入数据
insert into user values (null, '侯大利','1980-10-24','男','江州');
insert into user values (null, '田甜','1992-11-12','女','阳州');
insert into user values (null, '王永强','1983-05-20','男','阳州');
insert into user values (null, '杨红','1995-03-22','女','秦阳');

1.1.1 项目目录

添加依赖

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.guardwhy</groupId>
    <artifactId>03_Mybatis_CRUD</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <!--倒入项目所需依赖-->
    <dependencies>
        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.0</version>
        </dependency>
        <!--mysql连接依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
        <!--日志依赖-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--测试依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
    </dependencies>
</project>

db.properties

编写数据库连接属性资源文件(db.properties)放在resources资源文件下。

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_mybatis
jdbc.username=root
jdbc.password=root

sqlMapConfig.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>

    <!--在内部配置属性:先读取内部的属性,再读取外部的属性,外部的会覆盖内部的,最后外部的属性起作用-->
    <properties resource="db.properties">
        <property name="jdbc.username" value="root"/>
        <property name="jdbc.password" value="root"/>
    </properties>

    <!--定义实体类别名-->
    <typeAliases>
        <!--
        package指定包名
        1. 自动将这个包下所有的实体类定义别名,别名就是类的名字。(在日志输出中会有乱码,不用理会,不影响使用,这是mybatis的bug)
        2. 如果有多个子包,只需要指定父包即可。
        3. 可以使用多个package标签,指定不同的包名
        -->
        <package name="cn.guardwhy.domain"/>
    </typeAliases>

    <!-- 一个核心配置文件,可以配置多个运行环境,default默认使用哪个运行环境 -->
    <environments default="default">
        <!-- 其中的一个运行环境,通过id来进行标识-->
        <environment id="default">
            <!--
            事务管理器type的取值:
            1. JDBC:由JDBC进行事务的管理
            2. MANAGED:事务由容器来管理,后期学习Spring框架的时候,所有的事务由容器管理
            -->
            <transactionManager type="JDBC"/>
            <!--
            数据源:
            1. POOLED:使用mybatis创建的连接池
            2. UNPOOLED:不使用连接池,每次自己创建连接
            3. JNDI:由服务器提供连接池的资源,我们通过JNDI指定的名字去访问服务器中资源。
            -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--映射器-->
    <mappers>
        <!--
        package表示扫描这个包下所有的映射文件
        1. 接口与映射文件在要同一个目录下
        2. 接口的名字与映射文件名字要相同
        -->
        <package name="cn.guardwhy.dao"/>
    </mappers>
</configuration>

log4j.properties(日志)

### 设置Logger输出级别和输出目的地 ###
log4j.rootLogger=debug, stdout

### 把日志信息输出到控制台 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout

1.1.2 实体类

User类

package cn.guardwhy.domain;

import java.sql.Date;
/**
 * 用户实体类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
}

QueryVo类

package cn.guardwhy.domain;

/**
 * 包装类
 */
public class QueryVo {
    private User user; // 包含用户对象
    private String start; // 开始日期
    private String end; // 结束日期

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getStart() {
        return start;
    }

    public void setStart(String start) {
        this.start = start;
    }

    public String getEnd() {
        return end;
    }

    public void setEnd(String end) {
        this.end = end;
    }
}

1.2 查询用户(select)

1.2.1 普通查询

通过id查询一个用户

package cn.guardwhy.dao;

import cn.guardwhy.domain.User;

/**
 * 1. 添加User findUserById(Integer id)方法
 * 2. 参数使用引用类型
 */
public interface UserMapper {
    /**
     *  通过id查询一个用户
     */
    User findUserById(Integer id);
}

UserMapper.xml

  1. 添加select查询标签,并且设置属性id、parameterType、resultType
  2. 编写查询语句,使用占位符#
  3. 在resources资源下创建cn/guardwhy/dao目录
<?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">

<!--实体类的映射文件 namespace 指定接口的类全名-->
<mapper namespace="cn.guardwhy.dao.UserMapper">
    <!--
    查询语句id: 接口中方法的名字, resultType:返回的实体类的类型类全名, parameterType: 参数的类型
    -->
    <select id="findUserById" resultType="cn.guardwhy.domain.User" parameterType="java.lang.Integer">
        select * from user where id = #{id}
    </select>
</mapper>

测试代码

package cn.guardwhy.test;
/*
1. 声明静态成员变量:SqlSessionFactory
2. 成员变量:SqlSession
3. 成员变量:UserMapper
4. 在@BeforeClass中创建工厂对象
5. 在@Before中创建会话对象和userMapper对象
6. 在@Test中编写通过id查询用户的方法
*/
import cn.guardwhy.dao.UserMapper;
import cn.guardwhy.domain.User;
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.BeforeClass;
import org.junit.Test;

import java.io.InputStream;

/**
 * 测试类
 */
public class TestUserDao {
    // 会话工厂
    private static SqlSessionFactory factory;
    // 会话
    private SqlSession session;
    // 接口
    private UserMapper userMapper;

    /**
     * 类加载的时候执行一次,创建会话工厂
     */
    @BeforeClass
    public static void init() throws Exception{
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(inputStream);
    }

    /**
     * 每个测试方法前都会执行的方法
     */
    @Before
    public void begin(){
        session = factory.openSession(true);	//true设置为自动提交
        // 创建代理对象
        userMapper = session.getMapper(UserMapper.class);
    }

    @Test
    public void testFindUserById(){
        User user = userMapper.findUserById(1);
        System.out.println(user);
    }
}

1.2.2 模糊查询

typeAliases(别名)

作用:给用户自定义的实体类定义别名

单个别名配置

<!--定义实体类单个别名-->
    <!--
    typeAlias:
        1.type:指定实体类全名
        2.alias: 指定别名,如果省略这个属性,默认使用类名字做为别名,别名不区分大小写,通常别名使用小写。
    -->
<typeAlias type="cn.guardwhy.domain.User"/>

包扫描配置别名

<!--定义实体类别名-->
<typeAliases>
    <!--
        package指定包名
        1. 自动将这个包下所有的实体类定义别名,别名就是类的名字。(在日志输出中会有乱码,不用理会,不影响使用,这是mybatis的bug)
        2. 如果有多个子包,只需要指定父包即可。
        3. 可以使用多个package标签,指定不同的包名
     -->
    <package name="cn.guardwhy.domain"/>
</typeAliases>

UserMapper.xml

加载单个映射文件mapper

注:如果是多级目录,是/而不是点号

<!--
指定外面的实体类映射文件resource: 加载类路径下映射文件,不是点号,是/
url: 可以绝对路径的方式访问映射文件.
class: 导入接口类名,用于注解配置的方式 "cn.guardwhy.dao.UserMapper"    
-->
<mapper resource="cn/guardwhy/dao/UserMapper.xml"/>

包扫描加载映射

包扫描方式加载mapper映射文件:

  • 要求mapper映射文件,与mapper接口要放在同一个目录
  • 要求mapper映射文件的名称,与mapper接口的名称要一致
<!--
package表示扫描这个包下所有的映射文件
1. 接口与映射文件在要同一个目录下
2. 接口的名字与映射文件名字要相同
-->
<package name="cn.guardwhy.dao"/>

通过用户名查询用户

List<User> findUsersByName(String username);

UserMapper.xml

  1. 使用字符串拼接符${value}拼接参数
  2. 字符串的参数类型此处只能使用value名字
  3. 模糊查询前后使用%,外面使用单引号
<!--
通过名字模糊查询
${value}: 字符串拼接,使用$. 如果是简单类型:(8种基本类型+String类型),这里变量名只能是value
-->
<select id="findUsersByName" parameterType="String" resultType="user">
   select * from user where username like '%${value}%'
</select>

1.2.3 测试代码

package cn.guardwhy.test;

import cn.guardwhy.dao.UserMapper;
import cn.guardwhy.domain.User;
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.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

/**
 * 测试类
 */
public class TestUserDao {
    // 会话工厂
    private static SqlSessionFactory factory;
    // 会话
    private SqlSession session;
    // 接口
    private UserMapper userMapper;

    /**
     * 类加载的时候执行一次,创建会话工厂
     */
    @BeforeClass
    public static void init() throws Exception{
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(inputStream);
    }

    /**
     * 每个测试方法前都会执行的方法
     */
    @Before
    public void begin(){
        session = factory.openSession();
        // 创建代理对象
        userMapper = session.getMapper(UserMapper.class);
    }

    //通过id查询1个用户
    @Test
    public void testFindUserById(){
        User user = userMapper.findUserById(1);
        System.out.println(user);
    }

    // 通过名字模糊查询
    @Test
    public void testFindUserByName(){
        List<User> users = userMapper.findUsersByName("%大%");
        users.forEach(System.out::println);
    }

    // 用完后关闭会话
    @After
    public void end(){
        session.close();
    }
}

1.3 新增用户(insert)

新增用户

 // 3.新增用户
int addUser(User user);

UserMapper.xml

  1. 新增用户使用insert标签
  2. 放置新增sql语句,参数类型使用User
  3. 占位符使用user对象的各个#
<!--
 添加用户
 因为增删改没有查询的结果集,所以不用配置resultType。有返回值,返回影响的行数。
-->
<insert id="addUser" parameterType="user">
    insert into user values (null, #{username},#{birthday},#{sex},#{address})
</insert>

1.3.1 测试代码

插入新的记录

/**
 添加1个用户
 在mybatis中增删改,默认是手动提交事务
 1. 设置成自动提交 factory.openSession(true);
 2. 自己手动提交 session.commit();
*/
@Test
public void testAddUser(){
    User user = new User(null, "老江", Date.valueOf("1975-03-10"),"男","江州");
    int row = userMapper.addUser(user);
    System.out.println("添加了" + row + "行");
}

// 用完后关闭会话
@After
public void end(){
    session.commit();	// 自己手动提交
    session.close();
}

1.3.2 提交事务

如果Java程序代码执行成功,但是数据库中并没有新增记录。原因是没有提交事务,在对数据库的更新操作中(增、删、改)要求提交事务

自动提交事务

factory.openSession(true)

手动提交事务

session.commit()

1.3.3 查询新增记录主键值

子元素 selectKey

属性 说明
keyColumn 主键在表中对应的列名
keyProperty 主键在实体类中对应的属性名
resultType 主键的数据类型
order BEFORE: 在添加语句前执行查询主键的语句
AFTER: 在添加语句后执行查询主键的语句

映射文件

mysql中的函数:last_insert_id() 得到最后添加的主键

<!--
 添加用户
 因为增删改没有查询的结果集,所以不用配置resultType。有返回值,返回影响的行数。
-->
<insert id="addUser" parameterType="user">
    insert into user values (null, #{username},#{birthday},#{sex},#{address})
    <!--
        keyColumn:主键在表中对应的列名
        keyProperty:主键在实体类中对应的属性名
        resultType:主键的数据类型
          order:
            BEFORE: 在添加语句前执行查询主键的语句,AFTER: 在添加语句后执行查询主键的语句.
     -->
    <selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
        select last_insert_id()
    </selectKey>
</insert>

UserMapper.xml

直接在insert标签中增加属性的方式,只适合于支持自动增长主键类型的数据库,比如MySQL或SQL Server

<insert id="addUser" parameterType="user" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
  insert into user values (null,#{username},#{birthday},#{sex},#{address})
</insert>

增加属性

属性 说明
useGeneratedKeys true 使得自动生成的主键
keyColumn 表中主键的列名
keyProperty 实体类中主键的属性名

1.3.4 测试代码

/**
通过getId()得到新增的主键值
*/
@Test
public void testAddUser(){
    // 得到主键的值
    Integer id = user.getId();
    System.out.println("生成主键的值:" + id);
}

1.3.5 万能的Map

假如实体类或者数据库的表,字段或者参数过多,应该适当的考虑使用Map!!!

新增用户

// 在接口方法中,参数直接传递Map
int addUser2(Map<String, Object> map);

UserMapper.xml

<!--
插入数据:编写sql语句的时候,需要传递参数类型,参数类型为map
-->
<insert id="addUser2" parameterType="map">
    insert into db_mybatis.user (id , username, birthday, sex, address) 
    values
    (#{userid1}, #{username1},#{birthday1},#{sex1},#{address1});
</insert>

测试代码

@Test
public void addUser2(){
    // 1.创建map集合
    Map<String, Object> map = new HashMap<String, Object>();
    // 2.向集合中添加元素
    map.put("userid1", 11);
    map.put("username1","kobe");
    map.put("birthday1", Date.valueOf("1993-06-19"));
    map.put("sex1", "男");
    map.put("address1", "番禺区");
    // 3.插入数据
    int row = userMapper.addUser2(map);
    System.out.println("添加了" + row + "行");

}

总结

Map传递参数,直接在sql中取出key即可。【paramterType="map"】

对象传递参数,直接在sql中取对象的属性即可!【parameterType="Object"】

只有一个基本类型参数的情况下,可以直接在SQL中取得。

1.4 修改用户(update)

根据用户Id修改用户

int updateUser(User user);

配置mapper映射文件

<!--
	1. 根据用户id修改用户其它属性
	2. 使用update标签:放置修改sql语句
	3. 占位符使用:#{属性名}
-->

<!--修改用户-->
<update id="updateUser" parameterType="user">
    update user set username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} where id = #{id}
</update>

1.4.1 测试代码

  1. 修改8号用户的名字,生日,性别,地址
  2. 更新用户对象
@Test
// 更新8号用户
public void testUpdateUser(){
    User user = new User(8, "田跃进", Date.valueOf("1976-05-10"), "男", "秦阳");
    int row = userMapper.updateUser(user);
    System.out.println("更新了" + row + "行");
}

1.5 删除用户(delete)

根据用户id删除用户

int deleteUser(Integer id);

配置mapper映射文件

<!--
1. 根据用户Id删除用户
2. delete标签:放置删除sql语句
-->
<delete id="deleteUser" parameterType="int">
    delete from user where id = #{id}
</delete>

1.5.1 测试代码

// 删除用户
@Test
public void testDeleteUser(){
    int row = userMapper.deleteUser(12);
    System.out.println("删除了" + row + "行记录");
}

1.6 生命周期和作用域

Mybatis的生命周期

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

SqlSessionFactoryBuilder

  • 一旦创建了 SqlSessionFactory,就不需要它了。
  • 局部变量。

SqlSessionFactory

  • 说白了可以把它想象为数据库连接池。
  • SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在, 没有任何理由丢弃它或重新创建另一个实例
  • 因此SqlSessionFactory的最佳作用域是应用作用域。
  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession

  • 连接到连接池的一个请求。
  • SqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
  • 用完之后需要赶紧关闭,否则资源被占用!!

注意: 这里面的每一个Mapper,就代表一个具体的业务。

posted @ 2021-12-27 11:01  guardwhy  阅读(33)  评论(0编辑  收藏  举报