package com.example.zhang;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import com.example.zhang.pojo.User;
public class JdbcTest {
public static void main(String[] args) {
PreparedStatement preparedStatement;
ResultSet resultSet;
try {
// 1. 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取数据库连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "root");
// 3. 定义sql
String sql = "select * from user where id = ?";
// 4. 获取预处理statement
preparedStatement = connection.prepareStatement(sql);
// 5. 设置参数(序号从1开始)
preparedStatement.setInt(1, 2);
// 6. 执行sql
resultSet = preparedStatement.executeQuery();
// 7. 遍历结果集
while (resultSet.next()) {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setName(resultSet.getString("name"));
user.setPwd(resultSet.getString("pwd"));
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
通过JDBC操作数据库的代码,可以看出有一些不好的地方:
而Mybatis就是来解决掉这些问题的。
Mybatis是将数据库连接信息写在配置文件中,不存在硬编码问题;
mybatis执行的sql语句也是通过配置文件进行配置,不需要写在Java代码中;
mybatis的连接池管理、缓存管理等让数据库和查询数据效率更高
创建测试表
CREATE TABLE `user` (
id INT(10) NOT NULL,
NAME VARCHAR(20) DEFAULT NULL,
pwd VARCHAR(20) DEFAULT NULL,
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO USER (id, NAME, pwd) VALUES
(1, '张三', '123456'),
(2, '李四', 'abcd'),
(3, '王五', 'abc123');
导包
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.6version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.5version>
dependency>
创建对应的javaBean
package com.example.zhang.pojo;
public class User {
private int id;
private String name;
private String pwd;
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return this.pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "{" +
" id='" + getId() + "'" +
", name='" + getName() + "'" +
", pwd='" + getPwd() + "'" +
"}";
}
}
创建mybatis核心配置文件
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=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="com/example/zhang/dao/userMapper.xml"/>
mappers>
configuration>
编写Mybatis工具类
package com.example.zhang.utils;
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
// MyBatis工具类
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
/*
根据全局配置文件,利用SqlSessionFactoryBuilder创建SqlSessionFactory
*/
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/*
使用SqlSessionFactory获取SqlSession对象。
一个SqlSession对象代表和数据库的一次会话。
*/
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
编写DAO接口类(命名为Mapper)
package com.example.zhang.dao;
import java.util.List;
import com.example.zhang.pojo.User;
public interface UserMapper {
List<User> selectUser();
}
编写Mapper的xml配置文件(以前是写DAO接口实现类,现在不需要,只写对应的xml配置文件)
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.zhang.dao.UserMapper">
<select id="selectUser" resultType="com.example.zhang.pojo.User">
select * from user
select>
mapper>
编写测试类
package com.example.zhang;
import java.util.List;
import com.example.zhang.dao.UserMapper;
import com.example.zhang.pojo.User;
import com.example.zhang.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;
/**
* Unit test for simple App.
*/
public class AppTest {
@Test
public void selectUserTest() {
SqlSession session = MyBatisUtils.getSqlSession();
// 方法一 不推荐
// List users = session.selectList("com.example.zhang.dao.UserMapper.selectUser");
// 方法二
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selectUser();
for (User user : users) {
System.out.println(user);
}
session.close();
}
}
说明
MyBatis的配置文件包含了影响MyBatis行为甚深的设置(settings)和属性(properties)信息。
能配置的内容如下:(元素节点必须按顺序,否则会报错)
prop.driver=com.mysql.jdbc.Driver
prop.url=jdbc:mysql:///mybatis
prop.username=root
prop.password=root
数据库连接参数可以在外部进行配置,并可以进行动态替换。
我们既可以在典型的Java属性文件中配置这些属性(就是db.properties)
<properties resource="db.properties">properties>
也可以在外部属性文件的基础上,在properties元素的子元素中设置
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
properties>
最后这些设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值,比如
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
还可以在SqlSessionFactoryBuilder.build()方法中传入属性值,例如:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
// ... 或者 ...
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);
如果属性在不只一个地方进行了配置,那么Mybatis将按照下面的顺序来加载:
settings属性设置是Mybatis中极为重要的,它们会改变Mybatis的运行时行为
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响所有映射器中配置的缓存的全局开关 | true|false | true |
lazyLoadingEnabled | 延迟加载的全局开关,当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态 | true|false | false |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则映射,即从经典数据库列名A_COLUMN到经典Java属性名aColumn的类似映射 | true|false | false |
logImpl | 指定Mybatis缩影日志的具体实现,未指定时将自动查找 | SLF4J |LOG4J |LOG4J2 |JDK_LOGGING |COMMONS_LOGGING |STDOUT_LOGGING |NO_LOGGING | 未设置 |
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
settings>
类型别名是为Java类型设置一个短的名字,可以方便我们引用某个类。
它仅用于XML配置,存在的意义在于降低冗余的全限定类名书写。比如:
<typeAliases>
<typeAlias type="com.example.zhang.Employee" alias="employee"/>
<typeAlias type="com.example.zhang.Department" alias="department"/>
typeAliases>
当这样配置后,employee
和department
可以用在任何使用com.example.zhang.Employee
和com.example.zhang.Department
的地方
在类很多的情况下,可以批量设置别名,为这个包下的每一个类创建一个默认的别名,默认是简单类名小写,若有注解@Alias,则别名为注解值
<typeAliases>
<package name="com.example.zhang" />
typeAliases>
@Alias("hello")
public class Employee {
...
}
若是这样,Employee类的别名就是hello。
需要注意的是,Mybatis已经为许多常见的Java类型内建了相应的别名,它们都是大小写不敏感的,我们在起别名时不可占用已有的别名
无论是Mybatis在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成Java类型。
我们也可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。
Mybatis每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。
默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法来实例化。
如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。
插件是Mybatis提供的一个非常强大的机制,我们可以通过插件来修改Mybatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。(以后再学)
environment
标签进行配置并指定唯一标识符environments
标签中的default属性指定一个环境的标识符来快速切换环境<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=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
实际开发中,我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置
在自动查找资源方面,Java并没有提供一个很好的解决方案,所以最好的方法是直接告诉Mybatis去哪里找映射文件,mappers映射器就是用来定义映射SQL语句文件的。
我们可以使用相对于类路径的资源引用,或完全限定资源定位符(包括file:///
形式的URL),或类名和包名。
使用相对于类路径的资源引用:
<mappers>
<mapper resource="com/example/zhang/dao/userMapper.xml"/>
mappers>
使用完全限定资源定位符(URL):
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
mappers>
使用映射器接口实现类的完全限定类名(需要配置文件名称和接口名称一致,并且位于同一目录下):
<mappers>
<mapper class="com.example.zhang.dao.UserMapper"/>
mappers>
将包内的映射器接口实现全部注册为映射器(需要配置文件名称和接口一致,并且位于统一目录下):
<mappers>
<package name="com.example.zhang.dao" />
mappers>
映射文件指导着Mybatis如何进行数据库增删改查,有着非常重要的意义。
SQL映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.zhang.dao.UserMapper">
<select id="selectUser" resultType="com.example.zhang.pojo.User">
select * from user
select>
mapper>
namespace:命名空间,必须跟某个接口同名,接口中的方法与映射文件中sql语句id应该一一对应。
select标签是mybatis中最常用的标签之一
select语句有很多属性可以详细配置每一条SQL语句
重点关注几个:
需求:根据id查找用户
在接口类UserMapper中添加对应的接口方法
public interface UserMapper {
List<User> selectUser();
// 根据ID查找用户
User findUserById(int id);
}
在映射文件UserMapper.xml中添加select语句
<mapper namespace="com.example.zhang.dao.UserMapper">
<select id="selectUser" resultType="com.example.zhang.pojo.User">
select * from user
select>
<select id="findUserById" resultType="com.example.zhang.pojo.User">
select * from user where id = #{id}
select>
mapper>
编写测试代码
@Test
public void findUserByIdTest() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findUserById(2);
System.out.println(user);
session.close();
}
数据变更语句insert,update和delete的实现非常接近,和select使用差不多。
需求一:增加一个用户
在UserMapper接口中新增对应的方法
public interface UserMapper {
List<User> selectUser();
// 根据ID查找用户
User findUserById(int id);
// 新增一个用户
int addUser(User user);
}
在映射文件UserMapper.xml中添加insert语句
<insert id="addUser" parameterType="com.example.zhang.pojo.User">
insert into user values(#{id}, #{name}, #{pwd})
insert>
编写测试代码
@Test
public void addUserTest() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = new User(5, "老虎", "abcd123");
userMapper.addUser(user);
session.commit();
session.close();
}
注意:增,删,改操作需要提交事务。
需求二:修改用户信息
在UserMapper接口中新增对应的方法
public interface UserMapper {
List<User> selectUser();
// 根据ID查找用户
User findUserById(int id);
// 新增一个用户
int addUser(User user);
// 修改用户信息
int updateUser(User user);
}
在映射文件UserMapper.xml中添加update语句
<update id="updateUser" parameterType="com.example.zhang.pojo.User">
update user set name = #{name}, pwd = #{pwd} where id = #{id}
update>
编写测试代码
@Test
public void updateUserTest() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = new User(5, "大熊猫", "123456");
userMapper.updateUser(user);
session.commit();
session.close();
}
注意:增,删,改操作需要提交事务。
需求三:删除指定ID的用户
在UserMapper接口中新增对应的方法
public interface UserMapper {
List<User> selectUser();
// 根据ID查找用户
User findUserById(int id);
// 新增一个用户
int addUser(User user);
// 修改用户信息
int updateUser(User user);
// 删除指定用户
int deleteUserById(int id);
}
在映射文件UserMapper.xml中添加delete语句
<delete id="deleteUserById">
delete from user where id = #{id}
delete>
编写测试代码
@Test
public void deleteUserByIdTest() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
userMapper.deleteUserById(5);
session.commit();
session.close();
}
注意:增,删,改操作需要提交事务。
以上增删改操作都需要提交事务才生效,每次都是要在最后加上一句session.commit()
.
也可以直接在openSession时候默认自动提交事务,修改MyBatisUtils工具类:在openSession传递参数true
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession(true);
}
单个参数
可以接受基本类型,对象类型,集合类型的值,这种情况Mybatis可以直接使用这个参数,不需要经过任何处理
多个参数
任意多个参数,都会被Mybatis重新包装成一个Map传入,Map的key就是param1,param2…,值就是参数的值
命名参数
可以为参数使用@Param
起一个名字,Mybatis就会将这些参数封装金Map中,key就是我们自己指定的名字
Map(万能的map)
我们也可以封装多个参数为map,直接传递一个map
resultMap
**元素是Mybatis中最重要最强大的元素,它可以让你从90%的JDBCResultSets
数据提取代码中解放出来。下面来看一个例子:
数据库中三个字段属性名分别为:id,name,pwd
Java中实体类设计如下:
public class Customer {
private int id;
private String name;
private String password;
// 构造函数,get/set ,toString()省略 ...
}
接口方法
public interface CustomerMapper {
Customer selectCustomerById(int id);
}
mapper映射文件
<mapper namespace="com.example.zhang.dao.CustomerMapper">
<select id="selectCustomerById" resultType="com.example.zhang.pojo.Customer">
select * from user where id = #{id}
select>
mapper>
测试结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CcxCaYbc-1660187789917)(https://s2.loli.net/2022/03/26/HtyvOaREq6U3JIV.png)]
查询结果发现password为空,说明出现问题。
分析:
sql语句:select * from user where id = #{id}
实质上可以看做select id,name,pwd from user where id = #{id}
,mybatis会根据这些查询的列名(列名会转化为小写,数据库不区分大小写)去对应的实体类中查找相应列名的set方法设置值,由于找不到setPwd(),所以password返回null。
这种就是数据库列名与java类属性名不一致的表现,有这两种解决方案:
使用别名,为列名指定别名,别名和java类属性名一致
<select id="selectCustomerById" resultType="com.example.zhang.pojo.Customer">
select id,name,pwd as password from user where id = #{id}
select>
使用结果集映射ResultMap
<resultMap id="CustomerMap" type="com.example.zhang.pojo.Customer">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
resultMap>
<select id="selectCustomerById" resultMap="CustomerMap">
select * from user where id = #{id}
select>
我们在开发过程中,如果出现了异常需要排错,如果有日志,能帮助我们快速定位问题。
Mybatis通过使用内置的日志工程提供日志功能,内置的日志工程有以下几种实现:
Mybatis具体选择哪个日志实现工具是由mybatis的内置日志工程确定的,它会使用最先找到的(按以上的顺序查找),如果一个都没找到,就会禁用日志功能
不少应用服务器(如 Tomcat 和 WebShpere)的类路径中已经包含 Commons Logging。注意,在这种配置环境下,MyBatis 会把 Commons Logging 作为日志工具。这就意味着在诸如 WebSphere 的环境中,由于提供了 Commons Logging 的私有实现,你的 Log4J 配置将被忽略。这个时候你就会感觉很郁闷:看起来 MyBatis 将你的 Log4J 配置忽略掉了(其实是因为在这种配置环境下,MyBatis 使用了 Commons Logging 作为日志实现)。如果你的应用部署在一个类路径已经包含 Commons Logging 的环境中,而你又想使用其它日志实现,你可以通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择其它日志实现。
<configuration>
<settings>
...
<setting name="logImpl" value="LOG4J"/>
...
settings>
configuration>
logImpl可选的值有:
在Mybatis中具体使用哪一个日志实现,在设置settings中设置
标准日志实现
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
settings>
运行测试程序,终端会输出日志:
Log4j
简介:
使用步骤:
导入Log4j的包
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
编写配置文件
#将等级为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/zhang.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
setting设置日志实现为LOG4J
<settings>
<setting name="logImpl" value="LOG4J" />
settings>
测试程序中使用Log4j
public class AppTest {
// 参数为当前类
static Logger logger = Logger.getLogger(AppTest.class);
@Test
public void findUserByIdTest() {
logger.info("info: 进入findUserByIdTest方法");
logger.debug("debug: 进入findUserByIdTest方法");
logger.error("error: 进入findUserByIdTest方法");
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findUserById(2);
System.out.println(user);
session.close();
}
}
输出结果如下:
不仅终端生成了日志信息,还生成了一个日志文件,在指定路径里./log/zhang.log
思考:为什么需要分页?
在使用数据库时,会经常对数据库进行增删改查操作,其中使用最多的是查询操作,查询大量数据的时候,我们往往使用分页查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。
1. 使用Limit实现分页
语法:
select * from table limit startIndex, pageSize
select * from table limit 5, 10; # 检索记录行 6-15
# 为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为-1
select * from table limit 95, -1; # 检索记录行 96 - 末尾
# 如果只给定一个参数,它表示返回最大的记录行数目
select * from table limit 5; # 检索前5个记录行
# 相当于 limit n 等价于 limit 0, n
操作步骤:
添加接口方法,参数为map
public interface UserMapper {
// 查找用户
List<User> selectUser(Map<String,String> map);
}
修改Mapper文件
<select id="selectUser" parameterType="map" resultType="com.example.zhang.pojo.User">
select * from user limit #{startIndex}, #{pageSize}
select>
编写测试方法
推断:起始位置 = (当前页面 -1) 页面大小*
@Test
public void testSelectUser() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
int currentPage = 2; // 第几页
int pageSize = 2; // 每页显示几个
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("startIndex", (currentPage -1) * pageSize);
map.put("pageSize", pageSize);
List<User> users = userMapper.selectUser(map);
for (User u:users) {
System.out.println(u);
}
session.close();
}
2. RowBounds分页(了解即可)
使用RowBounds在Java代码层面实现分页
接口方法
List<User> getUserByRowBounds();
Mapper文件
<select id="getUserByRowBounds" resultType="com.example.zhang.pojo.User">
select * from user
select>
测试方法
@Test
public void testUserByRowBounds() {
SqlSession session = MyBatisUtils.getSqlSession();
int currentPage = 2; // 第几页
int pageSize = 2; // 每页显示几个
RowBounds rowBounds = new RowBounds((currentPage -1) * pageSize, pageSize);
// 通过session.* 方法传递rowBounds, 这种方法已经不推荐使用了
List<User> users = session.selectList("com.example.zhang.dao.UserMapper.getUserByRowBounds", null, rowBounds);
for (User u:users) {
System.out.println(u);
}
session.close();
}
3. PageHelper
这是一款Mybatis的分页插件,官网文档为:https://pagehelper.github.io/
Mybatis最初配置信息是基于XML的,映射语句SQL也是定义在XML中,但到了Mybatis 3提供了新的基于注解的配置,但Java注解的表达力和灵活性十分有限,并不能完全用注解来构建。
注解方式主要用于SQL,主要有几个注解:
@Select()
@Update()
@Insert()
@Delete()
注意:使用注解开发就不需要mapper.xml映射文件了
使用方法:
在接口方法中添加注解
@Select("select * from user")
List<User> selectUser();
mybatis核心配置文件,使用class绑定接口(不需要mapper.xml文件了)
<mappers>
<mapper class="com.example.zhang.dao.UserMapper"/>
mappers>
测试代码,与之前一样
@Test
public void selectUserTest() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selectUser();
for (User user : users) {
System.out.println(user);
}
}
注解方式实现增删改查:
注意增删改需要提交事务
编写接口方法
// 根据ID查找用户
@Select("select * from user where id = #{id}")
@Results(id = "userMap", value = {
@Result(id=true,column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(column = "pwd", property = "password")
})
User findUserById(@Param("id")int id);
// 新增一个用户
@Insert("insert into user (id,name,pwd) values(#{id},#{name},#{password})")
@ResultMap(value = {"userMap"})
int addUser(User user);
// 修改用户信息
@Update("update user set name=#{name},pwd=#{password} where id = #{id} ")
int updateUser(User user);
// 删除指定用户
@Delete("delete from user where id = #{id}")
int deleteUserById(int id);
这里特意将数据库列名pwd与实体类属性名password设置为不一致,在XML配置中我们可以使用resultMap结果集映射的方式解决,但在注解方式中,就得使用注解@Results,@ResultMap,@Result
实现了
@Results()注解
:
有两个常用的参数,一个是id,一个是value。
id的作用在于唯一标记这个Results注解,在其他接口抽象方法中需要通过@Results注解来解决 属性名与字段名不一致问题时,能被引用。
value的作用在于使用@Result注解建立实体类与数据库表映射关系,可以写多个@Result,如果是主键字段,@Result注解中需要设置id=true,比如上面例子的id是主键,column代表数据库字段名,property代表实体类属性名
@ResultMap()注解:
用于引用@Results注解,value值为数据,是@Results注解的id值
测试方法
@Test
public void findUserByIdTest() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findUserById(2);
System.out.println(user);
session.close();
}
@Test
public void addUserTest() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = new User(9, "华南虎", "abcd123");
userMapper.addUser(user);
session.close();
}
@Test
public void updateUserTest() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = new User(5, "大熊猫", "123456789");
userMapper.updateUser(user);
session.commit();
session.close();
}
@Test
public void deleteUserByIdTest() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
userMapper.deleteUserById(5);
session.commit();
session.close();
}
关于注解@Param
@Param
注解用于给方法参数起一个名字,以下是使用原则总结:
# 与 $ 的区别
总结
使用注解和配置文件协同开发,才是Mybatis的最佳实践!
Mybatis详细的执行流程:
多对一的理解:
(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) VALUE(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);
两个表的对应关系:
(2)添加实体类:
public class Teacher {
private int id;
private String name;
// 构造器,get/set方法,toString 省略...
}
public class Student {
private int id;
private String name;
// 多个学生可以是同一个老师,多对一
private Teacher teacher;
// 构造器,get/set方法,toString 省略...
}
(3)编写实体类对应的Mapper接口类
无论有没有需求,都应该写上,以备后来之需
public interface TeacherMapper {
}
public interface StudentMapper {
}
(4) 编写对应的Mapper.xml文件
无论有没有需求,都应该写上,以备后来之需
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.zhang.dao.StudentMapper">
mapper>
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.zhang.dao.TeacherMapper">
mapper>
(5) 按查询嵌套处理(子查询)
给StudentMapper接口添加方法
public interface StudentMapper {
// 获取所有学生及对应老师信息
List<Student> getStudents();
}
编写对应的Mapper文件(记得要在mybatis-config.xml中注册Mapper)
<mapper namespace="com.example.zhang.dao.StudentMapper">
<select id="getStudents" resultMap="StudentTeacher">
select * from student
select>
<resultMap id="StudentTeacher" type="com.example.zhang.pojo.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" column="tid" javaType="com.example.zhang.pojo.Teacher" select="getTeacher">association>
resultMap>
<select id="getTeacher" resultType="com.example.zhang.pojo.Teacher">
select * from teacher where id = #{id}
select>
mapper>
测试
@Test
public void test3() {
SqlSession session = MyBatisUtils.getSqlSession();
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
List<Student> students = studentMapper.getStudents();
for (Student s: students) {
System.out.println(s);
}
session.close();
}
(6)按结果嵌套处理(联表查询)
接口方法
List<Student> getStudents1();
Mapper文件
<select id="getStudents1" resultMap="StudentTeacher1">
select s.id sid, s.name sname, t.name tname from student s, teacher t where s.tid = t.id
select>
<resultMap id="StudentTeacher1" type="com.example.zhang.pojo.Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="com.example.zhang.pojo.Teacher">
<result property="name" column="tname"/>
association>
resultMap>
(7)小结
按照查询进行嵌套处理就像SQL中的子查询
按照结果进行嵌套处理就像SQL中的联表查询
一对多的理解:
按查询嵌套处理(子查询)
(1) 实体类编写
public class Teacher {
private int id;
private String name;
private List<Student> students;
// 构造器,get/set方法,toString 省略...
}
public class Student {
private int id;
private String name;
private int tid;
// 构造器,get/set方法,toString 省略...
}
(2) TeacherMapper接口编写方法
public interface TeacherMapper {
// 获取指定老师,以及老师下的所有学生
public Teacher getTeacher(int id);
}
(3) 编写接口对应的Mapper文件
<select id="getTeacher" resultMap="TeacherStudent">
select * from teacher where id = #{id}
select>
<resultMap id="TeacherStudent" type="com.example.zhang.pojo.Teacher">
<collection property="students" javaType="ArrayList" ofType="com.example.zhang.pojo.Student"
column="id" select="getStudentByTeacherId">collection>
resultMap>
<select id="getStudentByTeacherId" resultType="com.example.zhang.pojo.Student">
select * from student where tid = #{id}
select>
(4)测试
@Test
public void test4() {
SqlSession session = MyBatisUtils.getSqlSession();
TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class);
Teacher teacher = teacherMapper.getTeacher(1);
System.out.println(teacher);
session.close();
}
按结果嵌套处理(联表查询)
(1)编写接口对应的Mapper文件
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid,s.name sname, t.name tname,t.id tid from student s,teacher t where s.tid = t.id and t.id = #{id}
select>
<resultMap id="TeacherStudent" type="com.example.zhang.pojo.Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="com.example.zhang.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
collection>
resultMap>
介绍
什么是动态SQL?
动态SQL指的是根据不同的查询条件,生成不同的SQL语句
引用官方描述:
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。 虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。 动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。 MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。 MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。 ------------------------------- - if - choose (when, otherwise) - trim (where, set) - foreach -------------------------------
搭建环境
创建数据库
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
创建IDUtils工具类(用来生产博客id)
import java.util.UUID;
public class IDUtils {
public static String genId() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
编写实体类
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
// 省略get/set toString()犯法
// ...
}
**注意这里的属性createTime与数据库的列create_time不是一致!**后面做处理
编写Mapper接口类,添加一个方法,新增博客,用于初始化
public interface BlogMapper {
// 新增一个博客
public int addBlog(Blog blog);
}
编写对应的Mapper.xml
<mapper namespace="com.example.zhang.dao.BlogMapper">
<insert id="addBlog">
insert into blog(id, title, author, create_time, views)
values(#{id}, #{title}, #{author}, #{createTime}, #{views});
insert>
mapper>
mybatis-config核心配置文件,增加设置:下划线驼峰自动转换,用来解决上面数据库列名create_time和实体类属性名createTime不一致问题,会自动转换。
注册mapper文件
<setting name="mapUnderscoreToCamelCase" value="true" />
....
<mapper class="com.example.zhang.dao.BlogMapper"/>
因为ID使用了随机的UUID生产,这里先初始化一些数据库数据
@Test
public void addBlogTest() {
SqlSession session = MyBatisUtils.getSqlSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDUtils.genId());
blog.setAuthor("张三");
blog.setTitle("Mybatis如此简单");
blog.setCreateTime(new Date());
blog.setViews(9999);
blogMapper.addBlog(blog);
blog.setId(IDUtils.genId());
blog.setTitle("Java如此简单");
blogMapper.addBlog(blog);
blog.setId(IDUtils.genId());
blog.setTitle("Spring如此简单");
blogMapper.addBlog(blog);
blog.setId(IDUtils.genId());
blog.setTitle("Python如此简单");
blogMapper.addBlog(blog);
session.close();
}
if 标签
需求:根据作者名字和博客名字来查询博客,如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
编写接口类
// 使用万能Map作为传参
List<Blog> queryBlogIf(Map map);
编写SQL语句
<select id="queryBlogIf" parameterType="map" resultType="com.example.zhang.pojo.Blog">
select * from blog where
<if test="title != null">
title = #{title}
if>
<if test="author != null">
and author = #{author}
if>
select>
测试代码
@Test
public void testQueryBlogIf() {
SqlSession session = MyBatisUtils.getSqlSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<>();
map.put("title", "Mybatis如此简单");
map.put("author", "张三");
List<Blog> blogs = blogMapper.queryBlogIf(map);
System.out.println(blogs);
session.close();
}
分析:
上面查询结果是两个条件都提供了,title和author,也能正确查询出结果。
但是只用了if
语句,再看这个SQL语句,如果author为null,title不为null,那查询语句就会变成:select * from blog where title = #{title}
,刚好可以执行。
如果title为null的情况下呢?那查询语句就变成了select * from blog where and author = #{author}
,这就是错误的sql语句了,显然这样子只依赖if
语句是不行的,需要配合另一个where
语句,下面讲解。
where 标签
修改上面的SQL语句,使用where
标签
<select id="queryBlogIf" parameterType="map" resultType="com.example.zhang.pojo.Blog">
select * from blog
<where>
<if test="title != null">
title = #{title}
if>
<if test="author != null">
and author = #{author}
if>
where>
select>
where标签会知道如果它包含的标签中有返回值的话,他就插入一个where。如果标签返回的内容是以and或者or开头的,它就会将and或or剔除,保证sql的正确
测试
title 为null,author不为null
@Test
public void testQueryBlogIf() {
SqlSession session = MyBatisUtils.getSqlSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<>();
//map.put("title", "Mybatis如此简单");
map.put("author", "张三");
List<Blog> blogs = blogMapper.queryBlogIf(map);
System.out.println(blogs);
session.close();
}
测试结果:
可以看到确实将where给加上了,sql语句变为:select * from blog where title = ?
而且结果也是正常的返回。
set标签
同样的,在select语句中会有where关键字,那如果是更新操作,含有的set关键字,就要使用set
标签了。
编写接口方法
int updateBlog(Map map);
编写sql配置文件
<update id="updateBlog" 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关键字,并会删除额外的逗号
测试
@Test
public void testUpdateBlog() {
SqlSession session = MyBatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String,String> map = new HashMap<String,String>();
map.put("id", "2156717bb4894bb5bfc5dc82f6030c70");
map.put("title", "动态SQL");
map.put("author", "李四");
mapper.updateBlog(map);
session.close();
}
choose标签
有时候我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用choose标签可以解决此类问题,类似于java的switch语句
编写接口
List<Blog> queryBlogChoose(Map map);
编写SQL配置文件
<select id="queryBlogChoose" parameterType="map" resultType="com.example.zhang.pojo.Blog">
select * from blog
<where>
<choose>
<when test="title != null">
title = #{title}
when>
<when test="author != null">
and author = #{author}
when>
<otherwise>
and views = #{views}
otherwise>
choose>
where>
select>
测试
@Test
public void testQueryBlogChoose() {
SqlSession session = MyBatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("title", "Java如此简单");
// map.put("author", "张三");
// map.put("views", 9999);
List<Blog> blogs = mapper.queryBlogChoose(map);
System.out.println(blogs);
session.close();
}
foreach标签
foreach元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量,它也允许指定开头和结尾的字符串以及集合项迭代之间的分隔符,这个元素也不会错误的添加多余的分隔符。
你可以将任何可迭代对象(如List,Set等),Map对象或者数组对象作为集合参数传递给foreach,当使用可迭代对象或者数组时,index是当前迭代的序号,item的值是本次迭代获取到的元素,当使用map对象时,index是键,item是值
示例:
先将数据库的前三个数据的id修改为固定的1,2,3
需求:我们需要查询blog表中id分别为1,2,3的博客信息
编写接口
List<Blog> queryBlogForeach(Map map);
编写对应mapper文件
<select id="queryBlogForeach" parameterType="map" resultType="com.example.zhang.pojo.Blog">
select * from blog
where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
foreach>
select>
foreach一个常见使用场景是对集合进行遍历,尤其是在构建IN条件语句的时候
测试
@Test
public void testQueryBlogForeach() {
SqlSession session = MyBatisUtils.getSqlSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<>();
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids", ids);
List<Blog> blogs = blogMapper.queryBlogForeach(map);
System.out.println(blogs);
session.close();
}
SQL片段
有时候可能某个sql语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
抽取SQL片段:
<sql id="if-title-author">
<if test="title!=null">
title = #{title}
if>
<if test="author!=null">
and author = #{author}
if>
sql>
引用SQL片段:
<select id="queryBlogIf" parameterType="map" resultType="com.example.zhang.pojo.Blog">
select * from blog
<where>
<include refid="if-title-author">include>
where>
select>
注意:
- 最好基于单表来定义sql片段,提高片段的可重用性
- 在sql片段中不要包括where
Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
一级缓存
一级缓存也叫本地缓存:
测试:(加入日志,方便查看结果)
编写接口方法
User findUserById(@Param("id")int id);
接口对应的mapper文件
<select id="findUserById" resultType="com.example.zhang.pojo.User">
select * from user where id = #{id}
select>
测试
@Test
public void test() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findUserById(1);
System.out.println(user);
User user2 = userMapper.findUserById(1);
System.out.println(user);
System.out.println(user == user2);
session.close();
}
结果分析
一级缓存失效的四种情况
一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它。
sqlSession不同
@Test
public void test5() {
SqlSession session = MyBatisUtils.getSqlSession();
SqlSession session1 = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
UserMapper userMapper1 = session1.getMapper(UserMapper.class);
User user = userMapper.findUserById(1);
System.out.println(user);
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
System.out.println(user == user1);
session.close();
session1.close();
}
运行结果:发送了两条SQL语句
结论:每个sqlsession中的缓存相对独立
sqlSession相同,查询条件不同
@Test
public void test5() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
// UserMapper userMapper1 = session.getMapper(UserMapper.class);
User user = userMapper.findUserById(1);
System.out.println(user);
User user1 = userMapper.findUserById(2);
System.out.println(user1);
System.out.println(user == user1);
session.close();
}
运行结果:发送了两条SQL语句
结论:当前缓存中,不存在这个数据,就没法用缓存数据直接读取
sqlSession相同,两次查询之间执行了增删改操作
增加接口方法:
int updateUser(Map map);
编写mapper文件:
<update id="updateUser" parameterType="map">
update user set name = #{name} where id = #{id}
update>
测试:
@Test
public void test6() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findUserById(1);
System.out.println(user);
HashMap<String,Object> map = new HashMap<>();
map.put("id", 2);
map.put("name", "哪吒");
userMapper.updateUser(map);
User user1 = userMapper.findUserById(1);
System.out.println(user1);
System.out.println(user == user1);
session.close();
}
运行结果:同一个查询在中间执行了增删改的操作后,重新执行了
结论:因为增删改操作可能会对当前数据产生影响,所以不会直接读取缓存的了
sqlSession相同,手动清除一级缓存
@Test
public void test7() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.findUserById(1);
System.out.println(user);
// 手动清除缓存
session.clearCache();
// 再重新查询
User user1 = mapper.findUserById(1);
System.out.println(user1);
System.out.println(user == user1);
session.close();
}
运行结果:
二级缓存
使用步骤:
开启全局缓存(在核心配置文件中mybatis-config.xml)
<setting name="cacheEnabled" value="true" />
在每个要使用二级缓存的mapper.xml中配置
<cache>cache>
也可以自定义参数(官方例子)
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
代码测试
注意:必须将所有实体类实现序列化接口,否则会报错
org.apache.ibatis.cache.CacheException:Error serializing object. Cause: java.io.NotSerializableException: com.example.zhang.pojo.User
User类加上序列化接口:
public class User implements Serializable
测试代码:(查询完关闭一级缓存,再进行一次查询)
@Test
public void testCache() {
SqlSession session = MyBatisUtils.getSqlSession();
SqlSession session2 = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
User user = userMapper.findUserById(1);
System.out.println(user);
session.close();
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
System.out.println(user == user2);
session2.close();
}
测试结果:
总结
缓存原理
自定义缓存
可以使用第三方缓存实现–EhCache
Ehcache是一种广泛使用的java分布式缓存,用于通用缓存,以后缓存会用Redis。