Mybatis 从入门到入土

1.入门样例

1.1 建表

CREATE DATABASE mybatis;

USE mybatis;

CREATE TABLE USER(
	id INT(20) NOT NULL AUTO_INCREMENT,
	NAME VARCHAR(30) DEFAULT NULL,
	PASSWORD VARCHAR(30) DEFAULT NULL,
	PRIMARY KEY(id)
);

INSERT INTO USER VALUES(NULL,'张三','123456'),(NULL,'李四','123456'),(NULL,'王五','123456');

SELECT * FROM USER;

1.2 第一个mybatis项目

  • 依赖导入

        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.46</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.7</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.4</version>
            </dependency>
        </dependencies>
    <!--静态资源放行(使得非resource下的配置文件也可以被打入jar/war包)-->
        <build>
            <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>true</filtering>
                </resource>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>true</filtering>
                </resource>
            </resources>
        </build>
    
  • MybatisUtil工具类,用于获得sqlsession对象

    public class MybatisUtil {
    
        private static SqlSessionFactory sqlSessionFactory;
        static {
            try {
                String url = "mybatis-config.xml";
                InputStream resourceAsStream = Resources.getResourceAsStream(url);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static SqlSession getSqlSession(){
            return sqlSessionFactory.openSession();
        }
    }
    
  • Mybatis的核心配置文件编写

    <?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://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
    	<!--useSSL是高版本才必须填写,useUnicode是指程序增加到数据库的编码是utf-8-->
        <!--配置每个mapper对应的xml文件,路径间用/隔开-->
        <mappers>
            <mapper resource="com/kuang/Mapper/UserMapper.xml"/>
        </mappers>
    </configuration>
    
  • pojo的javabean对象编写(用lombook缩减代码量)

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private int id;
        private String name;
        private String password;
    }
    
  • 编写mapper接口

    public interface UserMapper {
        public List<User> findAll();
    }
    
  • 编写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">
    <!--namespace 一定为对应的mapper的全类名-->
    <mapper namespace="com.kuang.Mapper.UserMapper">
    <!--resultType只要写类型就行了,不用写泛型(List)-->
        <select id="findAll" resultType="com.kuang.pojo.User">
            select * from user ;
        </select>
    </mapper>
    
  • 测试

    public class UserMapperTest {
        @Test
        //第一种方式
        public void test1(){
            SqlSession sqlSession = MybatisUtil.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> all = mapper.findAll();
            for (User user : all) {
                System.out.println(user);
            }
    
        }
        @Test
        //第二种方式
        public void test2(){
            SqlSession sqlSession = MybatisUtil.getSqlSession();
            List<User> list = sqlSession.selectList("com.kuang.Mapper.UserMapper.findAll");
            for (User user : list) {
                System.out.println(user);
            }
    
        }
    }
    

2.CRUD

2.1 xml文件中的属性设置

  1. 中的包名要和Dao/Mapper接口的包名一致
  2. :就是对应namespace的方法名
  3. : sql语句执行的返回值
  4. : 参数类型(int类型就不用加了)

2.2 正删改查实现

mapper

public interface UserMapper {

    public List<User> findAll();

    public User findById(int id);

    public int addUser(User user);

    public int updateUser(User user);

    public int deleteUserById(int id);
}

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">
<!--namespace 一定为对应的mapper的全类名-->
<mapper namespace="com.kuang.Mapper.UserMapper">
    <insert id="addUser" parameterType="com.kuang.pojo.User">
        insert into USER
        values (null ,#{name},#{password});
    </insert>
    <update id="updateUser">
        update user
        set name = #{name} ,password = #{password}
        where id = #{id};
    </update>
    <delete id="deleteUserById">
        delete
        from user
        where id = #{id};
    </delete>
    <!--resultType只要写类型就行了,不用写泛型(List)-->
    <select id="findAll" resultType="com.kuang.pojo.User">
        select * from user ;
    </select>
    <!--若参数是基本类型,则#{}中可以随便写变量名,但最好写方法参数的名称-->
    <select id="findById" resultType="com.kuang.pojo.User">
        select * from user where id = #{id};
    </select>
</mapper>

测试–>增删改都需要提交事务

    //增删改都需要提交事务
    @Test
    public void testAddUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.addUser(new User(0,"何骏扬","123456"));
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void testUpdateUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.updateUser(new User(4,"何志刚","123456"));
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void testDeleteUserById(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteUserById(4);
        sqlSession.commit();
        sqlSession.close();
    }

2.3 分析错误

  • 标签名不要匹配错
  • resource绑定mapper,需要用路径 ,例:com/kuang/Mapper/UserMapper.xml
  • 程序配置文件必须符合规范
  • 输出的xml中有中文乱码,在设置->编辑器->文件编码中设置
  • maven静态资源导出问题

2.4 万能Map使用

使用map作为sql的参数可以实现灵活地调用属性,从而避免了只单一用pojo对象来操作数据库,可以添加其他参数

如: select * from user where id = ? and name like % ? %;

第二个参数就不在pojo实体类中,这时候可以用map解决问题

基本使用

public int addUser2(Map<String,Object> map);
    <!--#{}中的内容和键名一一对应-->
	<insert id="addUser2" parameterType="map">
        insert into user
        values (null,#{username},#{password});
    </insert>
    @Test
    public void testMapAddUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("username","旺旺");//和xml中的内容对应
        map.put("password","123456");//和xml中的内容对应
        mapper.addUser2(map);
        sqlSession.commit();
        sqlSession.close();
    }

2.5 模糊查询

  1. java代码实现
public List<User> findLikeName(String name);
    <select id="findLikeName" resultType="com.kuang.pojo.User" parameterType="string">
        select * from USER where name like #{string};
    </select>
    @Test
    public void testFindLikeName(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> likeName = mapper.findLikeName("%王%");
        for (User user : likeName) {
            System.out.println(user);
        }
        sqlSession.close();
    }
  1. xml文件实现
public List<User> findLikeName(String name);
    <!--%必须写成"%"-->
	<!--#{}将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号-->
	<select id="findLikeName" resultType="com.kuang.pojo.User" parameterType="string">
        select * from USER where name like "%"#{string}"%";
    </select>
    @Test
    public void testFindLikeName(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> likeName = mapper.findLikeName("王");
        for (User user : likeName) {
            System.out.println(user);
        }
        sqlSession.close();
    }

3.配置解析

3.1 核心配置文件

  • mybatis-config.xml
  • Mybatis 的配置文件包含了会深深影响Mybatis行为的设置和属性信息.
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

3.1 环境配置

基本上是事务管理器JDBC,数据源类型POOLED

<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://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

3.2 属性

  • 可以导入一个外部的配置文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=root
<!--resource的配置文件若有多层文件夹,用'/'隔开-->
<properties resource="database.properties"/>
  • 可以在中间再加上一些属性(这里面的属性名如果和配置文件里面的属性名一样,优先使用配置文件)
    <properties resource="database.properties">
        <property name="username" value="root"/>
    </properties>

更换environment的设置

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

3.3 类型别名

  • 每次对一个实体类进行别名映射
    <typeAliases>
        <typeAlias type="com.kuang.pojo.User" alias="User"/>
    </typeAliases>
  • 对一个包下的所有类进行别名映射,默认别名为该类的类名首字母小写
    <typeAliases>
        <package name="com.kuang.pojo"/>
    </typeAliases>

也可以用**@Alias**来指定该类的别名(用注解加包扫描是唯一一种idea不会爆红的写法…)

@Alias("theUser")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private String password;
}
  • Mybatis内置了很多别名,如 _int --> int string --> String
    • 如果是基本类型,如int ,别名为 _ + 类型 如_int --> int
    • 如果是分装类,如Integer,String 为 首字母小写 如 integer --> Integer string --> String

3.4 设置

一个日志输出的日志,可以输出sql语句的执行情况

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

3.5 映射器

  • 方法一
    <mappers>
        <mapper resource="com/kuang/Mapper/UserMapper.xml"/>
    </mappers>
  • 方法二
    <mappers>
        <mapper class="com.kuang.Mapper.UserMapper"/>
    </mappers>

注意事项:

1.mapper和配置文件再同一个包下

2.mapper和配置文件同名

  • 方法三(可以注册该包下所有的mapper)
    <mappers>
        <package name="com.kuang.Mapper"/>
    </mappers>

注意事项(同上):

1.mapper和配置文件再同一个包下

2.mapper和配置文件同名

3.6 作用域和生命周期

SqlSessionFactoryBuilder:

  • 一旦创建了 SqlSessionFactory,就不再需要它了
  • 最佳作用域是方法作用域(也就是局部方法变量)

SqlSessionFactory:

  • 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
  • 最佳作用域是应用作用域(全局)

SqlSession:

  • 最佳的作用域是请求或方法作用域
  • 一旦使用完毕,就要关闭

4 解决属性名和字段名不一致

4.1 问题

当出现pojo实体类中属性和数据库中表不一致

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;//不一致的属性
}

则查询会出现

User(id=1, name=张三, pwd=null)

4.2 原因

  1. 首先sql语句把信息从数据库中查出来,以字段名为键,数据为值存储在ResultSet中
  2. mybatis提供了自动映射和配置映射
  3. 自动映射将ResultSet中的键和pojo实体类中名称相同的成员变量相互映射
  4. 自动映射也可以将ResultSet中的键和HashMap的键相互映射,值相互映射
  5. 自动映射就是mybatis隐式配置了一个resultMap
  6. 配置映射可以指定字段名和成员变量映射

4.3 解决方案一:修改sql语句

<select id="findById" resultType="com.kuang.pojo.User">
    select id,name,password as pwd from user where id = #{id};
</select>

4.4 解决方案二:使用ResultMap进行配置映射

    <resultMap id="UserMap" type="com.kuang.pojo.User">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="password" property="pwd"/>
    </resultMap>
    <select id="findById" resultMap="UserMap">
        select * from user where id = #{id};
    </select>

由于有自动映射的原因,可以去掉可以自动映射的属性

<resultMap id="UserMap" type="com.kuang.pojo.User">
    <result column="password" property="pwd"/>
</resultMap>
<select id="findById" resultMap="UserMap">
    select * from user where id = #{id};
</select>

5 日志工厂

SLF4J

LOG4J

LOG4J2

JDK_LOGGING

COMMONS_LOGGING

STDOUT_LOGGING [默认配置]

NO_LOGGING

5.1 默认配置

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

log4j

  1. 导入依赖
    <dependencies>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
  1. 导入配置文件log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  1. mybatis核心文件配置
<?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="database.properties"/>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>

    <typeAliases>
        <package name="com.kuang.pojo"/>
    </typeAliases>

    <!--配置环境-->
    <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>

    <!--配置每个mapper对应的xml文件,路径间用/隔开-->
    <mappers>
        <mapper resource="com/kuang/Mapper/UserMapper.xml"/>
    </mappers>
</configuration>

简单使用

  • 用于打印控制台语句
    private static Logger logger = Logger.getLogger(the_test.class);//获取对象,记得不要导错包
    @Test
    public void test2(){
        logger.info("hello world");
        logger.debug("hello world");
        logger.error("hello world");
    }

6 分页

-- limit分页语法: select * from user limit startIndex , pageSize;
select * from user limit 2 , 2;
-- 这表示从第3条数据开始,选2个
select * from user limit 5 , 3;
-- 这表示从第6条数据开始,选3个

用map实现分页操作

 public List<User> findByLimit(Map<String,Object> map);
    <select id="findByLimit" parameterType="map" resultType="com.kuang.pojo.User">
        select * from user limit #{startIndex},#{pageSize};
    </select>
    @Test
    public void text4(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String,Object> map = new HashMap<>();
        map.put("startIndex",0);
        map.put("pageSize",2);
        List<User> byLimit = mapper.findByLimit(map);
        for (User user : byLimit) {
            System.out.println(user);
        }
        sqlSession.close();
    }

7 注解开发

7.1 基本使用

注解开发用于一些简单sql的编写,使用时绑定接口要用class的方式,所以若注解和配置混合开发的话,xml文件和mapper接口要在同一个包下

mapper

public interface UserMapper {
    @Select("select * from user")
    public List<User> findAll();

}

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>

    <properties resource="database.properties"/>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!--配置环境-->
    <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>

    <!--配置每个mapper对应的xml文件,路径间用/隔开-->
    <mappers>
        <mapper class="com.kuang.Mapper.UserMapper"/>
    </mappers>
</configuration>

测试

    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> all = mapper.findAll();
        for (User user : all) {
            System.out.println(user);
        }
        sqlSession.close();
    }

7.2 注解使用(有多个参数)

@Param()注解作用于方法的参数上,用于给这个参数给上一个确切的名字,用于多个基本类型参数

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不用加
  • 如果只有一个基本类型或者String,可以不加
  • 我们再sql中引用的是我们这里@Param("")中设置的属性名

当有多个引用类型的参数,用Map操作

    @Select("select * from user where id = #{user1.id}")
    public User findById(Map<String,User> map);
    @Test
    public void test2(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String,User> map = new HashMap<>();
        map.put("user1",new User(1,"221","213123"));
        map.put("user2",new User(2,"123","12213"));
        User byId = mapper.findById(map);
        System.out.println(byId);
        sqlSession.close();
    }

测试可行

7.3 CRUD

可以自动提交事务

public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession(true);
    }

UserMapper

public interface UserMapper {
    @Select("select * from user")
    public List<User> findAll();

    @Select("select * from user where id = #{id}")
    public User findById(@Param("id") int id);

    @Update("update user set name = #{name} , password = #{password} where id =#{id}")
    public int update(User user);

    @Insert("insert into user values(null,#{name},#{password})")
    public int insert(User user);

    @Delete("delete from user where id = #{id}")
    public int delete(@Param("id") int id);

}

测试

public class theTest {
    @Test
    public void test1(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> all = mapper.findAll();
        for (User user : all) {
            System.out.println(user);
        }
        sqlSession.close();
    }

    @Test
    public void test2(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User byId = mapper.findById(1);
        System.out.println(byId);
        sqlSession.close();
    }

    @Test
    public void test3(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.insert(new User(1, "呵呵", "111111"));
        sqlSession.close();
    }

    @Test
    public void test4(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.update(new User(1, "旺旺", "111111"));
        sqlSession.close();
    }

    @Test
    public void test5(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.delete(7);
        sqlSession.close();
    }
}

8 lombook

依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.4</version>
</dependency>
@Data//get和set,tostring,hascode,equals,默认的无参
@NoArgsConstructor//无参构造
@AllArgsConstructor//有参构造

9 多对一和一对多查询

9.1 多对一和一对多

  • 多个学生对应一个老师
  • 对于学生这边而言,关联,多个学生关联一个老师[多对一]
  • 对于老师而言,集合,一个老师,有很多学生[一对多]

9.2 测试环境搭建

9.2.1 建表

CREATE TABLE `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师'); 

CREATE TABLE `student` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (1, '小明', 1); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, '小红', 1); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, '小张', 1); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, '小李', 1); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, '小王', 1);

9.2.2 pojo

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private int id;
    private String name;
    private Teacher teacher; //学生这里的tid换成了一个老师的实体类
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {
    private int id;
    private String name;
}

9.2.3 主配置文件

<?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="database.properties"/>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!--配置环境-->
    <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>

    <!--配置每个mapper对应的xml文件,路径间用/隔开-->
    <mappers>
        <mapper class="com.kuang.mapper.StudentMapper"/>
        <mapper class="com.kuang.mapper.TeacherMapper"/>
    </mappers>
</configuration>

9.2.4 mapper.java

public interface StudentMapper {
    public Student findById(@Param("id") int id);
}

public interface TeacherMapper {
    public Teacher findById(@Param("id") int id);
}

9.2.5 mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.StudentMapper">
    <select id="findById" resultType="com.kuang.pojo.Student">
        select * from student where id = #{id};
    </select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.TeacherMapper">
    <select id="findById" resultType="com.kuang.pojo.Teacher">
        select * from teacher where id = #{id};
    </select>
</mapper>

9.3 多对一(查询学生)

pojo

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {
    private int id;
    private String name;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

第一种方式:嵌套 Select 查询

也就是子查询

mapper.java

    public List<Student> findAll1();

    public Teacher findTeacherById(int tid);

mapper.xml

<!--
        思路:
        1. 查询所有学生信息
        2. 用查询出来的学生信息,寻找对应的老师
    -->
    <select id="findAll1" resultMap="findMap1" >
        select * from student;
    </select>

    <resultMap id="findMap1" type="com.kuang.pojo.Student">
        <result property="name" column="name"/>
        <result property="id" column="id"/>
        <!--复杂的属性单独处理, 对象: association 集合: collection-->
        <!--这里的column直接作为后面select的参数传入-->
        <association property="teacher" column="tid" javaType="com.kuang.pojo.Teacher" select="findTeacherById"/>
    </resultMap>

    <select id="findTeacherById" resultType="com.kuang.pojo.Teacher">
        select * from teacher where id =#{tid}
    </select>

第二种方式:嵌套结果映射

也就是联表查询

mapper.java

public List<Student> findAll2();

mapper.xml

    <!--
        思路:
        1. 直接找两张表的信息笛卡尔积
        2.把字段映射到<association>中去
    -->
    <select id="findAll2" resultMap="findMap2">
        SELECT
            s.`id` sid,
            s.`name` sname,
            s.`tid` stid,
            t.`id` tid,
            t.`name` tname
        FROM
            student s,teacher t
        WHERE
            s.tid = t.id;
    </select>

    <resultMap id="findMap2" type="com.kuang.pojo.Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="com.kuang.pojo.Teacher">
            <result property="id" column="tid"/>
            <result property="name" column="tname"/>
        </association>
    </resultMap>

associate使用(用于一个对象处理)

关联(association)元素处理“有一个”类型的关系。 比如,在我们的示例中,一个博客有一个用户。关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。

关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:

  • 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
  • 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。

首先,先让我们来看看这个元素的属性。你将会发现,和普通的结果映射相比,它只在 select 和 resultMap 属性上有所不同。

关联的嵌套结果查询

属性 描述
property 映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
javaType 一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcType JDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。
typeHandler 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。

关联的嵌套 Select 查询

相对于嵌套结果查询的基础上增加的属性

属性 描述
column 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
select 用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的例子。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
fetchType 可选的。有效值为 lazyeager。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。

9.4 一对多(查询老师)

重新搭建测试环境

pojo

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private int id;
    private String name;
    private int tid;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {
    private int id;
    private String name;
    private List<Student> students; //一个老师对应多个学生
}

第一种方式:嵌套 Select 查询

mapper.java

    public Teacher findById2(int id);

    public List<Student> findStudentByTid(int id);

mapper.xml

<select id="findById2" resultMap="findById2Map">
        select * from teacher where id = #{id};
    </select>

    <resultMap id="findById2Map" type="com.kuang.pojo.Teacher">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!--此处javaType可以省略-->
        <collection property="students" javaType="List" ofType="com.kuang.pojo.Student" column="id" select="findStudentByTid"/>
    </resultMap>

    <select id="findStudentByTid" resultType="com.kuang.pojo.Student">
        select  * from student where tid = #{id};
    </select>

第二种方式:嵌套结果映射

mapper.java

    public Teacher findById(int id);

mapper.xml

   <select id="findById" resultMap="findByIdMap">
        select
            s.id sid,
            s.name sname,
            s.tid stid,
            t.id tid,
            t.name tname
        from student s,teacher t where s.tid = t.id;
    </select>

    <resultMap id="findByIdMap" type="com.kuang.pojo.Teacher">
        <result column="tid" property="id"/>
        <result property="name" column="tname"/>
        <collection property="students" ofType="com.kuang.pojo.Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>

9.5 小结

  1. 关联 -association 用于多对一(pojo中的成员变量是一个实体类)
  2. 集合 -collection 用于一对多(pojo中成员变量是一个集合或者列表)
  3. javaType和ofType
    1. javaType用于指定实体类中属性的类型
    2. ofType用于指定映射到List或set中的pojo类型

10 动态sql

10.1 环境搭建

  1. 建表
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8
  1. 主配置文件
<?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="database.properties"/>

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--配置驼峰映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <typeAlias type="com.kuang.pojo.Blog" alias="blog"/>
    </typeAliases>
    <!--配置环境-->
    <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>

    <!--配置每个mapper对应的xml文件,路径间用/隔开-->
    <mappers>
        <mapper class="com.kuang.dao.BlogMapper"/>
    </mappers>
</configuration>
  1. pojo
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;//这里设置了驼峰映射
    private int views;
}
  1. utils添加IdUtil
@SuppressWarnings("all") //可以抑制警告
public class IdUtil {
    public static String getId(){
        return UUID.randomUUID().toString().replace("-","");
    }
}
  1. mapper和mapper.xml
public interface BlogMapper {

    public int insert(Blog blog);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.BlogMapper">

    <insert id="insert" parameterType="blog">
        insert into blog
        values (#{id},#{title},#{author},#{createTime},#{views});
    </insert>
</mapper>
  1. 插入数据
public class MyTest {
    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        Blog blog = new Blog();
        blog.setId(IdUtil.getId());
        blog.setTitle("Mybatis如此简单");
        blog.setAuthor("狂神说");
        blog.setCreateTime(new Date());
        blog.setViews(9999);

        mapper.insert(blog);

        blog.setId(IdUtil.getId());
        blog.setTitle("Java如此简单");
        mapper.insert(blog);

        blog.setId(IdUtil.getId());
        blog.setTitle("Spring如此简单");
        mapper.insert(blog);

        blog.setId(IdUtil.getId());
        blog.setTitle("微服务如此简单");
        mapper.insert(blog);

        sqlSession.close();
    }
}

10.2 if标签的使用

mapper

public List<Blog> findBlogIf(Map<String,Object> map);
    <select id="findBlogIf" resultType="com.kuang.pojo.Blog" parameterType="map">
        select * from blog where 1=1
        <if test="author != null">
            and author = #{author}
        </if>
        <if test="title != null">
            and title = #{title}
        </if>
    </select>

其中,test中的变量是传入的参数,也就是#{}可以取出的东西

test="author != null",author就是一个变量

,如果map中没有author参数就判断失败…

10.3 where标签的使用

    <select id="findBlogWhere" resultType="com.kuang.pojo.Blog" parameterType="map">
        select * from blog
        <where>
            <if test="author != null">
                and author = #{author}
            </if>
            <if test="title != null">
                and title = #{title}
            </if>
        </where>
    </select>

其作用是动态添加where子句

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

所有可以在where标签中要拼接的sql子句前都加上and

10.4 set标签的使用

    <update id="update" parameterType="map">
        update blog
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author},
            </if>
        </set>
        where id = #{id}
    </update>

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

所有可以在set标签中要拼接的sql子句后都加,

10.5 trim标签的使用

<trim prefix="" prefixOverrides="" suffix="" suffixOverrides=""/>

prefix:前缀,若标签中有子句则加上前缀

prefixOverrides:若标签中拼接的语句的前缀是这个属性中设定的内容,则去除该内容

suffix:后缀,若标签中有子句则加上后缀

suffixOverrides:若标签中拼接的语句的后缀是这个属性中设定的内容,则去除该内容

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

10.6 choose标签的使用

该标签就是switch语句

    <select id="findChoose" resultType="com.kuang.pojo.Blog">
        select * from blog
        <where>
            <choose>
                <when test="title != null">and title = #{title}</when>
                <when test="author != null"> and author = #{author}</when>
                <otherwise></otherwise>
                <!--otherwise可以不用-->
            </choose>
        </where>
    </select>

10.7 sql和include标签的使用

<select id="findBlogWhere" resultType="com.kuang.pojo.Blog" parameterType="map">
        select * from blog
        <where>
            <include refid="if-author-title"/>
        </where>
    </select>


    <sql id="if-author-title">
        <if test="author != null">
            and author = #{author}
        </if>
        <if test="title != null">
            and title = #{title}
        </if>
    </sql>

可以把一些公用的sql片段拼解,实现代码复用

10.8 forEach标签使用

可以实现in的操作

<select id="findIn" resultType="com.kuang.pojo.Blog">
    select * from blog
    <where>
        <foreach collection="list" separator="," item="item" open="id in (" close=")" index="index">
            #{item}
        </foreach>
    </where>
</select>

你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。,(测试后,map不可以作为参数,只能map中含有list集合,用这个集合才行)

collection:集合的类型

separator:分隔符

item:每次迭代获取的值

index:每次的下标值

open:以什么开头

close:以什么结束

11 缓存

意义:减少与数据库的交互,降低资源的浪费

11.1 一级缓存(默认开启)

  • 一级缓存也叫本地缓存: sqlSession
    • 与数据库同一次会话期间查询到的数据会放在本地缓存中
    • 以后如果需要获取相同的数据,直接从缓存里面拿,没必要再去查询数据库;

测试

@Test
    public void test1(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User byId = mapper.findById(1);
        System.out.println(byId);
        User byId2 = mapper.findById(1);
        System.out.println(byId2);

    }

只会查询一次,因为查询会加入缓存

缓存失效的情况

  1. 查询不同的东西
  2. 增删改操作,可能改变原来数据,所以必定刷新缓存
  3. 查询不同的mapper,xml
  4. 手动清理缓存
sqlSession.clearCache();

11.2 二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存

  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭了,这个会话对应的一级缓存就没有了,但是会话关闭了,一级缓存中的数据被保存到二级缓存中去
    • 新的会话查询信息,就可以从二级缓存中获取内容
    • 不同的么mapper查询出来的数据会放在自己对应的缓存中;

在要加上二级缓存的支持的mapper.xml中添加

<cache/>

也可以

    <!--刷新时间为60秒,只读,可以存储对象或引用为512个引用,清除策略-->
    <cache flushInterval="60000"
           readOnly="true"
           size="512"
           eviction="FIFO"/>

测试

    @Test
    public void test2(){
        SqlSession sqlSession1 = MybatisUtil.getSqlSession();
        SqlSession sqlSession2 = MybatisUtil.getSqlSession();
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        User byId = mapper1.findById(1);
        System.out.println(byId);
        sqlSession1.close();
        User byId1 = mapper2.findById(1);
        System.out.println(byId1);
        sqlSession2.close();
    }

只在数据库查询了一次

上一篇:四、Mybatis 结果集映射、日志和分页


下一篇:第 27 章 CSS 传统布局[下]