Mybatis篇:初识Mybatis日志以及常用CRUD(中)

163 阅读9分钟

日志配置

配置日志的一个重要原因是想在调试的时候能观察到sql语句的输出,能查看中间过程

1、标准日志实现

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

STD:standard out:输出

STDOUT_LOGGING:标准输出日志

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

这就好了,执行一下看看。

2、组合logback完成日志功能(扩展)

使用步骤:

1、导入log4j的包

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${logback.version}</version>
</dependency>

2、配置文件编写 log4j.properties

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

    <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss} %c [%thread] %-5level %msg%n"/>
    <property name="log_dir" value="d:/logs" />

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.out</target>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--日志输出路径-->
        <file>${log_dir}/sql.log</file>
    </appender>

    <root level="ALL">
        <appender-ref ref="console"/>
    </root>

    <logger name="mybatis.sql" level="debug" additivity="false">
        <appender-ref ref="console"/>
        <appender-ref ref="file"/>
    </logger>

</configuration>

3、setting设置日志实现

<settings>
   <setting name="logImpl" value="SLF4J"/>
</settings>

四、CRUD来一套

1、基本流程:

// 1、创建一个SqlSessionFactory的 建造者 ,用于创建SqlSessionFactory
// SqlSessionFactoryBuilder中有大量的重载的build方法,可以根据不同的入参,进行构建
// 极大的提高了灵活性,此处使用【创建者设计模式】
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 2、使用builder构建一个sqlSessionFactory,此处我们基于一个xml配置文件
// 此过程会进行xml文件的解析,过程相对比较复杂
SqlSessionFactory sqlSessionFactory = builder.build(Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml"));
// 3、通过sqlSessionFactory获取另一个session,此处使用【工厂设计模式】
SqlSession sqlSession = sqlSessionFactory.openSession();

1、创建一个SqlSessionFactory的 建造者 ,用于创建SqlSessionFactory

2、使用builder构建一个sqlSessionFactory,此处我们基于一个xml配置文件

3、通过sqlSessionFactory获取另一个session,此处使用【工厂设计模式】

4、一个sqlsession就是一个会话,可以使用sqlsession对数据库进行操作,原理后边会讲。

其实第一次使用sqlsession我们可能会这么操作:

try (SqlSession sqlSession = sqlSessionFactory.openSession();){
    List<Object> objects = sqlSession.selectList("select * from user");
}

(1)SqlSessionFactory

​ 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

​ SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式。

(2)SqlSession

​ 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。

(3)测试

private SqlSessionFactory sqlSessionFactory = null;
private static final Logger LOGGER = LoggerFactory.getLogger(TestMybatis.class);

@Before
public void build() {
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    sqlSessionFactory = builder.build(Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml"));
}

当我们看到sqlSession有selectList,delete,update等方法时,我们会忍不住这样去使用。

@Test
public void testSession(){
    try (SqlSession sqlSession = sqlSessionFactory.openSession();){
        List<Object> objects = sqlSession.selectList("select * from user");
        System.out.println(objects);
    }
}

但是这样确实是错误的。

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for select * from user
### Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for select * from user

错误消息中显示Mapped Statements collection does not contain value for select * from user,说是mapper的申明中没有“select * from user”,其实这里让你填的是一个申明。我们在源码注释中可以看到:】

/**
* Retrieve a list of mapped objects from the statement key.
* @param <E> the returned list element type
* @param statement Unique identifier matching the statement to use.//与要使用的语句匹配的唯一标识符。  
* @return List of mapped object
*/
<E> List<E> selectList(String statement);

说明这个还需要通过使用sql的一个标识符。在MyBatis中我们还需要一个sql的映射文件来给每一个sql语句定义一个唯一标识符,我们起名UserMapper.xml,将这个文件放在resources文件夹下的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="UserMapper">
    <select id="selectOne" resultType="com.ydlclass.User">
        select * from user where id = #{id}
    </select>
</mapper>

还需要讲这个配置文件注册到主配置文件中,在最后边添加如下代码:

<mappers>
    <mapper resource="mapper/UserMapper.xml"/>
</mappers>

我们会发现每一个mapper映射文件都有一个命名空间,从下边的用法来看我们能大致明白命名空间的作用。就如同我们国家可以有相同名字的县,但是加上市以后我们就能很轻松的区别两个县,这个市就是县的命名空间。

try (SqlSession sqlSession = sqlSessionFactory.openSession();){
    List<Object> users = sqlSession.selectList("UserMapper.selectAll");
    LOGGER.debug("result is [{}]",objects);
}

我们得到了正确的结果:

image-20211104145927906

image-20211104145927906

当然不加命名空间也可,因为我们并没有其他重复的标识符,这个selectOne就是这条语句的标识符。

try (SqlSession sqlSession = sqlSessionFactory.openSession();){
    List<Object> users = sqlSession.selectList("selectAll");
    LOGGER.debug("result is [{}]",objects);
}

#(4)动态代理实现

但是MyBatis给我们提供了更好的解决方案,这种方案使用动态代理的技术实现,后边会详细讲。我们可以这样:

1、定义一个接口:

public interface UserMapper {
    List<User> selectAll();
}

2、修改映射文件,让命名空间改为接口的权限定名,id改为方法的名字

<mapper namespace="com.ydlclass.mapper.UserMapper">
    <select id="selectAll" resultType="com.ydlclass.entity.User">
        select * from user
    </select>
</mapper>

3、我们可以很简单的使用如下的方法操作:

try (SqlSession sqlSession = sqlSessionFactory.openSession();){
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> list = mapper.selectAll();
    LOGGER.debug("result is [{}]",list);
}

这样写起来简直不要太舒服,也确实拿到了对应的结果:

image-20211104145927906

image-20211104145927906

​ 这里很明显使用了动态代理的方式,sqlSession.getMapper(UserMapper.class);帮我们生成一个代理对象,该对象实现了这个接口的方法,具体的数据库操作比如建立连接,创建statment等重复性的工作交给框架来处理,唯一需要额外补充的就是sql语句了,xml文件就是在补充这个描述信息,比如具体的sql,返回值的类型等,框架会根据命名空间自动匹配对应的接口,根据id自动匹配接口的方法,不需要我们再做额外的操作。

接下来我们就把增删改查全部写一下,感受一下:

2、select(查询)

select标签是mybatis中最常用的标签

1、在UserMapper中添加对应方法

/**
     * 根据id查询用户
     * @param id
     * @return
     */
User selectUserById(int id);

2、在UserMapper.xml中添加Select语句

<select id="selectUserById" resultType="com.ydlclass.entity.User"  parameterType="int">
    select id,username,password from user where id = #{id}
</select>

新的知识点,在映射文件中有一些属性:

  • resultType:指定返回类型,查询是有结果的,结果啥类型,你得告诉我
  • parameterType:指定参数类型,查询是有参数的,参数啥类型,你得告诉我
  • id:指定对应的方法映射关系,就是你得告诉我你这sql对应的是哪个方法
  • #{id}:sql中的变量,要保证大括号的变量必须在User对象里有
  • #{}:占位符,其实就是咱们的【PreparedStatement】处理这个变量,mybatis会将它替换成?

除了#{}还有${},看看有啥区别,面试常问

  • #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】

    INSERT INTO user (username) VALUES (#{username});
    INSERT INTO user (username) VALUES (?);
    
  • ${} 的作用是直接进行字符串替换

    INSERT INTO user (username) VALUES ('${username}');
    INSERT INTO user (username) VALUES ('楠哥');
    

3、测试类中测试

try (SqlSession sqlSession = sqlSessionFactory.openSession();){
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.selectUserById(1);
    LOGGER.debug("the user is [{}]",user);
}

结果正确:

image-20211020153327382

image-20211020153327382

我们不妨把session的创建定义成一个方法:

private static SqlSession open(){
    // 1、创建一个SqlSessionFactory的 建造者 ,用于创建SqlSessionFactory
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    // 2、使用builder构建一个sqlSessionFactory,此处我们基于一个xml配置文件
    SqlSessionFactory sqlSessionFactory = builder.build(Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml"));
    // 3、通过sqlSessionFactory获取另一个session,此处使用【工厂设计模式】
    return sqlSessionFactory.openSession();
}

3、insert(插入)

insert标签被用作插入操作

1、接口中添加方法

/**
     * 新增user
     * @param user
     * @return
     */
int addUser(User user);

2、xml中加入insert语句

<insert id="addUser" parameterType="com.ydlclass.entity.User">
    insert into user (id,username,password) values (#{id},#{username},#{password})
</insert>

3、测试

@Test
public void testAdd(){
    SqlSession sqlSession = open();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int rows = mapper.addUser(new User(10, "lucy", "123"));
    LOGGER.debug("Affected rows: [{}]",rows);
}

返回值是1,你欣喜若狂,总以为就是这么简单,但事实是,数据库压根没有存进去:

image-20211020155233979

image-20211020155233979

注:增、删、改操作需要提交事务!在默认情况下MySQL的事务是自动提交的,而框架却默认设置成了手动提交,我们开启了事务,又没有去提交事务,结束后自然会回滚啊:

第一种方式,在openSession方法传入true,就变成自动提交了:

sqlSessionFactory.openSession(true);

第二种方式,我们手动提交,事实上我们肯定要手动提交事务:

@Test
public void testAdd(){
    SqlSession sqlSession = open();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int rows = mapper.addUser(new User(10, "lucy", "123"));
    LOGGER.debug("Affected rows: [{}]",rows);
    sqlSession.commit();
    sqlSession.close();
}

思考,如果参数没有传实体类而是传了多个参数,,能不能执行

比如数据库为id,方式传入userId

1、在UserMapper中添加对应方法

/**
     * 新增用户
     * @param id
     * @param name
     * @param pws
     * @return
     */
int insertUser(int id,String name,String pws);

2、在UserMapper.xml中添加Select语句

<insert id="insertUser" parameterType="com.ydlclass.entity.User">
    insert into user (id,username,password) values (#{id},#{username},#{password})
</insert>

3、测试

@Test
    public void testInsert(){
        SqlSession sqlSession = open();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int rows = mapper.insertUser(10, "lucy", "123");
        LOGGER.debug("Affected rows: [{}]",rows);
        sqlSession.commit();
        sqlSession.close();
    }

出问题了

 Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg2, arg1, arg0, param3, param1, param2]

这就无法映射了。

这就需要注解@Param了,这其实就是在做映射关系,xml里的${username}和方法中的name做映射:

int insertUser(@Param("id") int id, @Param("username") String name,@Param("password") String pws);

此时又一次成功了:

@Test
public void testInsert(){
    SqlSession sqlSession = open();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int rows = mapper.insertUser(11, "lucy", "123");
    LOGGER.debug("Affected rows: [{}]",rows);
    sqlSession.commit();
    sqlSession.close();
}

所以我们遇到mapper中有多个参数时,一定要使用@Param注解,建立映射关系。

4、update(修改)

update标签用于更新操作

1、写接口

/**
 * 修改用户
 * @param user
 * @return
 */
int updateUser(User user);

2、写SQL

<update id="updateUser" parameterType="com.ydlclass.entity.User">
    update user set username=#{username},password=#{password} where id = #{id}
</update>

3、测试

@Test
public void testUpdate(){
    SqlSession sqlSession = open();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int rows = mapper.updateUser(new User(11, "小微微", "123"));
    LOGGER.debug("Affected rows: [{}]",rows);
    sqlSession.commit();
    sqlSession.close();
}

5、delete(删除)

delete标签用于做删除操作

1、写接口

/**
* 删除一个用户
* @param id
* @return
*/
int deleteUser(int id);

2、写SQL

<delete id="deleteUser" parameterType="int">
  delete from user where id = #{id}
</delete>

3、测试

 @Test
public void testDeleteUser(){
    SqlSession sqlSession = open();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int affectedRows = mapper.deleteUser(5);
    LOGGER.debug("Affected rows: [{}]",affectedRows);
    sqlSession.commit();
    sqlSession.close();
}

6、模糊查询

方案一:在Java代码中拼串

string name = “%IT%”;
list<name> names = mapper.getUserByName(name);
<select id=”getUsersByName”>
	select * from user where name like #{name}
</select>

方案二:在配置文件中拼接

string name = “IT”;
list<User> users = mapper.getUserByName(name);
<select id=”getUsersByName”>
    select * from user where name like "%"#{name}"%"
</select>

为什么必须用双引号?

方案三:在配置文件中拼接

<select id=”getUsersByName”>
    select * from user where name like "%${name}%"
</select>

7、map的使用

map可以代替任何的实体类,所以当我们数据比较复杂时,可以适当考虑使用map来完成相关工作

1、写sql

<select id="getUsersByParams" parameterType="java.util.HashMapmap">
    select id,username,password from user where username = #{name}
</select>

2、写方法

/**
* 根据一些参数查询
* @param map
* @return
*/
List<User> getUsersByParams(Map<String,String> map);

3、测试

@Test
public void findByParams() {
    UserMapper mapper = session.getMapper(UserMapper.class);
    Map<String,String> map = new HashMap<String, String>();
    map.put("name","磊磊哥");
    List<User> users = mapper.getUsersByParams(map);
    for (User user: users){
        System.out.println(user.getUsername());
    }
}

五、使用注解开发

DROP TABLE IF EXISTS `admin`;
CREATE TABLE `admin` (
`id` int(20) NOT NULL,
`username` varchar(30) DEFAULT NULL,
`password` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `admin`(`id`,`username`,`password`) values (1,'itnanls','123456'),(2,'itlils','abcdef'),(3,'小微','987654');

​ MyBatis最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的。而到MyBatis 3提供了新的基于注解的配置。不幸的是,Java 注解的的表达力和灵活性十分有限。最强大的 MyBatis 映射并不能用注解来构建,所以这里我们作为了解。

  • sql 类型主要分成 :
  • @select ()
  • @update ()
  • @Insert ()
  • @delete ()

**注意:**利用注解开发就不需要mapper.xml映射文件了 .

1、接口中添加注解

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin {

    private static final Long serialVersionUID = 1L;

    private int id;
    private String username;
    private String password;
}
public interface AdminMapper {

    /**
     * 保存管理员
     * @param admin
     * @return
     */
    @Insert("insert into admin (username,password) values (#{username},#{password})")
    int saveAdmin(Admin admin);

    /**
     * 跟新管理员
     * @param admin
     * @return
     */
    @Update("update admin set username=#{username} , password=#{password} where id = #{id}")
    int updateAdmin(Admin admin);

    /**
     * 删除管理员
     * @param admin
     * @return
     */
    @Delete("delete from admin where id=#{id}")
    int deleteAdmin(int id);

    /**
     * 根据id查找管理员
     * @param id
     * @return
     */
    @Select("select id,username,password from admin where id=#{id}")
    Admin findAdminById(@Param("id") int id);

    /**
     * 查询所有的管理员
     * @return
     */
    @Select("select id,username,password from admin")
    List<Admin> findAllAdmins();

}

2、核心配置文件中配置

添加一个mapper的配置:

<mapper class="com.ydlclass.mapper.AdminMapper"/>

3、进行测试

public class TestAdminMapper {

    @Test
    public void testSaveAdmin() {
        SqlSession session = open();
        AdminMapper mapper = session.getMapper(AdminMapper.class);
        Admin admin = new Admin(1,"微微姐","12345678");
        int affectedRows = mapper.saveAdmin(admin);
         LOGGER.debug("Affected rows: [{}]",affectedRows);
        session.commit();
        session.close();
    }

    @Test
    public void testUpdateAdmin() {
        SqlSession session = open();
        AdminMapper mapper = session.getMapper(AdminMapper.class);
        Admin user = new Admin(1,"磊磊哥","12345678");
        int affectedRows = mapper.updateAdmin(user);
        LOGGER.debug("Affected rows: [{}]",affectedRows);
        session.commit();
        session.close();
    }

    @Test
    public void testDeleteAdmin(){
        SqlSession session = open();
        AdminMapper mapper = session.getMapper(AdminMapper.class);
        int affectedRows = mapper.deleteAdmin(2);
        LOGGER.debug("Affected rows: [{}]",affectedRows);
        session.commit();
        session.close();
    }

    @Test
    public void testGetAdminById(){
        SqlSession session = open();
        AdminMapper mapper = session.getMapper(AdminMapper.class);
        Admin admin = mapper.findAdminById(1);
        LOGGER.debug("The admin is: [{}]",admin);
        session.commit();
        session.close();
    }

    @Test
    public void testGetAllAdmins(){
        SqlSession session = open();
        AdminMapper mapper = session.getMapper(AdminMapper.class);
        List<Admin> admins = mapper.findAllAdmins();
        LOGGER.debug("The admins is: [{}]",admin);
        session.commit();
        session.close();
    }

    private static SqlSession open(){
        // 1、创建一个SqlSessionFactory的 建造者 ,用于创建SqlSessionFactory
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        // 2、使用builder构建一个sqlSessionFactory,此处我们基于一个xml配置文件
        SqlSessionFactory sqlSessionFactory = builder.build(Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml"));
        // 3、通过sqlSessionFactory获取另一个session,此处使用【工厂设计模式】
        return sqlSessionFactory.openSession();
    }

}