<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>x.x.xversion>
dependency>
package com.meimeixia.mybatis.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcTest {
//1. 加载驱动
//2. 创建连接
//3. 设置SQL语句
//4. 创建一个Statement对象
//5. 设置参数
//6. 执行查询,得到一个ResultSet对象
//7. 遍历结果集,输出结果
//8. 释放资源
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "liayun");
// 定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
// 获取预处理statement
preparedStatement = connection.prepareStatement(sql);
// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "王五");
// 向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
// 遍历查询结果集
while (resultSet.next()) {
System.out.println(resultSet.getString("id")+" "+ resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
数据库连接的创建、释放频繁造成资源浪费从而影响系统性能,如果使用数据库连接池可以解决这个问题。
代码不易维护,失去了一旦变动,据需要修改java代码
String sql = "select * from user where id = ?";
preparedStatement = connection.prepareStatement(sql);
使用preparedStatement
向占位符传参存在硬编码,因为SQL语句的where条件不确定,可能多也可能少,修改SQL语句还要修改Java代码,导致系统不易维护。
preparedStatement.setString(1,"mike");
对结果集解析存在硬编码(查询列名),SQL语句变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析则比较方便。
// 遍历查询结果集
while (resultSet.next()) {
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.3version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。
<configuration>
<properties resource="jdbc.properties"/>
<typeAliases>
<package name="domain"/>
typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC">transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="UserMapper.xml">mapper>
mappers>
configuration>
jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=1996613nba
SqlSessionFactory
SqlSessionFactory
的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。SqlSessionFactory
的实例为核心的。SqlSessionFactory
的实例可以通过SqlSessionFactoryBuilder
获得。而 SqlSessionFactoryBuilder
则可以从 XML 配置文件或一个预先配置的 Configuration
实例来构建出 SqlSessionFactory
实例。编写一个获取SqlSession
对象的工具类以方便获取对象
package com.yy.utils;
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 java.io.IOException;
import java.io.InputStream;
/**
* @author YuanYong([email protected])
* @date 2020/3/23 0:30
* @description:TODO
*/
public class MybatisUtils {
/**
* 获取SqlSessionFactory对象
*/
private static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis-comfig.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
/**
* 返回SqlSession对象
*/
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
使用Lombok
package com.yy.pojo;
import lombok.Data;
import java.util.Date;
/**
* @author YuanYong(1218639030 @ qq.com)
* @date 2020/3/23 0:51
* @description:TODO
*/
@Data
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List<Order> orderList;
}
UserDao.java
package com.yy.mapper;
import com.yy.pojo.User;
import java.util.List;
/**
* @author YuanYong(1218639030 @ qq.com)
* @date 2020/3/23 1:00
* @description:TODO
*/
public interface UserMapper {
List<User> getUserList();
}
UserMapper.xml
<mapper namespace="com.yy.dao.UserMapper">
<select id="getUserList" resultType="com.yy.pojo.User">
select * from user where 1 = 1
select>
mapper>
为了这个简单的例子,我们似乎写了不少配置,但其实并不多。在一个 XML 映射文件中,可以定义无数个映射语句,这样一来,XML 头部和文档类型声明部分就显得微不足道了。文档的其它部分很直白,容易理解。
<mappers>
<mapper resource="com/yy/dao/UserMapper.xml">mapper>
mappers>
import com.yy.dao.UserDao;
import com.yy.pojo.User;
import com.yy.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
/**
* @author YuanYong(1218639030 @ qq.com)
* @date 2020/3/23 1:17
* @description:TODO
*/
public class Mybatis {
@Test
public void test(){
//1.获取sqlSession连接
SqlSession session = MybatisUtils.getSqlSession();
//2.获取mapper
UserDao userDao = session.getMapper(UserDao.class);
List<User> list = userDao.getUserList();
//3.遍历结果集
for (User user:list) {
System.out.println(user);
}
//4.关闭连接
session.close();
}
}
测试结果
User(id=1, username=王五, birthday=null, sex=2, address=null)
User(id=10, username=张三, birthday=Thu Jul 10 00:00:00 CST 2014, sex=1, address=北京市)
User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州)
User(id=22, username=陈小明, birthday=null, sex=1, address=河南郑州)
User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州)
User(id=25, username=陈小明, birthday=null, sex=1, address=河南郑州)
User(id=26, username=王五, birthday=null, sex=null, address=null)
User(id=29, username=苑勇, birthday=Sat Jan 18 00:00:00 CST 2020, sex=1, address=青岛)
创建接口
User selectUserById(Integer id);
在mapper.xml
文件中写入查询语句
<select id="selectUserById" parameterType="Integer" resultType="com.yy.pojo.User">
select * from user where id = #{id}
select>
测试
@Test
public void selectUserById(){
//获取SqlSession连接
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper对象
UserMapper mapper = session.getMapper(UserMapper.class);
//执行方法
User user = mapper.selectUserById(1);
//打印结果
System.out.println(user);
//关闭连接
session.close();
}
结果
User(id=1, username=王五, birthday=null, sex=2, address=null)
创建接口
Integer insertUser(User user);
在mapper.xml
文件中写入查询语句
<insert id="insertUser" parameterType="com.yy.pojo.User">
<selectKey keyProperty="id" resultType="Integer" order="AFTER">
SELECT LAST_INSERT_ID()
selectKey>
insert into
user(username, birthday, sex, address)
values (#{username},#{birthday},#{sex},#{address});
insert>
属性 | 描述 |
---|---|
keyProperty |
selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn |
返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
resultType |
结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。 |
order |
可以设置为 BEFORE 或 AFTER 。如果设置为 BEFORE ,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER ,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。 |
statementType |
和前面一样,MyBatis 支持 STATEMENT ,PREPARED 和 CALLABLE 类型的映射语句,分别代表 Statement , PreparedStatement 和 CallableStatement 类型。 |
测试
@Test
public void insertUser(){
User user = new User();
user.setUsername("CodeYuan-Y");
Date date = new Date();
user.setBirthday(date);
user.setSex("男");
user.setAddress("青岛市");
//获取SqlSession连接
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper对象
UserMapper mapper = session.getMapper(UserMapper.class);
//执行sql
mapper.insertUser(user);
//提交事务
session.commit();
//输出自增id
System.out.println("自增id为:"+user.getId());
//关闭连接
session.close();
}
结果
自增id为:35
创建接口
Integer updateUser(User user);
Sql语句
<update id="updateUser" parameterType="com.yy.pojo.User">
update user set username = #{username} where id = #{id}
update>
测试
@Test
public void updateUser(){
User user = new User();
user.setId(1);
user.setUsername("CodeYuan-Y");
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper对象
UserMapper mapper = session.getMapper(UserMapper.class);
//执行方法
mapper.updateUser(user);
//提交事务
session.commit();
//关闭连接
session.close();
}
换一种写法…
假设我们的实体类,或者数据库中的表、字段过多,我们的参数应当考虑Map
创建接口
Integer deleteUser(Map<String,Object> map);
在mapper.xml·
中创建sql
<delete id="deleteUser" parameterType="map">
delete from user where id = #{userid} and username = #{username}
delete>
测试
@Test
public void deleteUser(){
Map<String,Object> map = new HashMap<String, Object>();
map.put("userid",29);
map.put("username","苑勇");
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper对象
UserMapper mapper = session.getMapper(UserMapper.class);
//调用方法
mapper.deleteUser(map);
//提交事务
session.commit();
//关闭连接
session.close();
}
创建接口
List<User> selectUserByLike(String s);
java代码中传递通配符。
mapper.xml
配置
<select id="selectUserByLike" parameterType="java.lang.String" resultType="com.yy.pojo.User">
select * from user where username like #{value}
select>
测试
@Test
public void selectUserByLike(){
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper = session.getMapper(UserMapper.class);
//传入参数
List<User> list = mapper.selectUserByLike("%张%");
//提交事务
session.commit();
//遍历结果集
for (User user:list) {
System.out.println(user);
}
//关闭连接
}
结果
User(id=10, username=张三, birthday=Thu Jul 10 00:00:00 CST 2014, sex=1, address=北京市)
User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州)
User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州)
sql中拼接通配符(容易sql注入)
mapper.xml
配置
<select id="selectUserByLike" parameterType="java.lang.String" resultType="com.yy.pojo.User">
select * from user where username like "%"#{value}"%"
select>
测试
@Test
public void selectUserByLike(){
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper = session.getMapper(UserMapper.class);
//传入参数
List<User> list = mapper.selectUserByLike("张");
//提交事务
session.commit();
//遍历结果集
for (User user:list) {
System.out.println(user);
}
//关闭连接
session.close();
}
结果
User(id=10, username=张三, birthday=Thu Jul 10 00:00:00 CST 2014, sex=1, address=北京市)
User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州)
User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州)
默认情况下,使用 #{}
参数语法时,MyBatis 会创建 PreparedStatement
参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:
ORDER BY ${columnName}
这样,MyBatis 就不会修改或转义该字符串了。
在mybatis-config.xml
文件中,可把数据库连接信息配置到properties标签当中。
<properties resource="jdbc.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>
MyBatis将按照下面的顺序来加载属性:
说得通俗一点就是:先加载property元素内部的属性,然后再加载jdbc.properties文件外部的属性,如果有同名属性则会覆盖。可以将properties元素内部jdbc.username属性的值故意写错,即故意将数据库连接的用户名给整错,结果发现仍然好使,这足以说明问题了。
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书 。
要注意的是:别名不区分大小写 UsEr 和 user效果是一样的
<typeAliases>
<typeAlias type="com.yy.pojo.User" alias="user" />
typeAliases>
配置别名后,mapper里面的一些参数类型和返回类型就可以使用别名
原来的写法
<update id="updateUser" parameterType="com.yy.pojo.User">
update user set username = #{username} where id = #{id}
update>
现在的写法
<update id="updateUser" parameterType="user">
update user set username = #{username} where id = #{id}
update>
如果像这样为每一个pojo定义一个别名,那不是傻逼吗!万一一个项目里面有很多pojo类呢?所以我们可以批量定义别名,就像下面这样。
<typeAliases>
<package name="com.yy.pojo"/>
typeAliases>
Mapper(映射器)的配置有如下三种方式:
第一种方式:使用
标签来加载相对于类路径的映射文件
第二种方式:使用
标签配置映射文件的class扫描器,也就是说加载Mapper接口类路径下的映射文件。
注意:这种方式要求Mapper接口名称和Mapper映射文件名称相同,且放在同一个目录中。
第三种方式:使用
标签配置映射文件包扫描,注册指定包下的所有Mapper接口,也就是说能加载指定包下的所有映射文件了。
注意:这种方式也要求Mapper接口名称和Mapper映射文件名称相同,且放在同一个目录中。
虽然Mapper(映射器)配置有以上三种方式,但是在实际开发中就用第三种方法,其他方式仅做了解就行!
第三种实例
<mappers>
<package name="com.yy.mybatis.mapper"/>
mappers>
第三种方式IDEA maven项目默认不会把src下除java文件外的文件打包到classes文件夹下,需要在pom.xml中增加配置
src/main/java **/*.xml配置日志打印可以更好的帮助我们查看过程,发现错误,以下是STDOUT_LOGGING
配置
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
输出日志实例
Created connection 33105141.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f924f5]
==> Preparing: select * from user where username like "%"?"%"
==> Parameters: 张(String)
<== Columns: id, username, birthday, sex, address
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Row: 24, 张三丰, null, 1, 河南郑州
<== Total: 3
resultType
虽然可以指定将查询结果映射为pojo,但需要pojo的属性名和SQL语句查询的列名一致方可映射成功。如果SQL语句查询字段名和pojo的属性名不一致,那么可以通过resultMap
将字段名和属性名作一个对应关系,resultMap
实质上还是需要将查询结果映射到pojo对象中。resultMap
可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和List
集合就能分别实现一对一查询和一对多查询。
创建接口
List<User> getUserListMap();
配置映射
<resultMap id="user_list_map" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
resultMap>
<select id="getUserListMap" resultMap="user_list_map">
select
id,username,sex,address
from user
select>
测试
@Test
public void getUserListMap(){
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper = session.getMapper(UserMapper.class);
//调用方法
List<User> userListMap = mapper.getUserListMap();
//提交事务
session.commit();
//输出结果集
for (User u:userListMap) {
System.out.println(u);
}
//关闭连接
session.close();
}
结果
User(id=1, username=CodeYuan-Y, birthday=null, sex=2, address=null)
User(id=10, username=张三, birthday=null, sex=1, address=北京市)
User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州)
User(id=22, username=陈小明, birthday=null, sex=1, address=河南郑州)
User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州)
User(id=25, username=陈小明, birthday=null, sex=1, address=河南郑州)
User(id=26, username=王五, birthday=null, sex=null, address=null)
User(id=35, username=CodeYuan-Y, birthday=null, sex=男, address=青岛市)
constructor
- 用于在实例化类时,注入结果到构造方法中
idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg
- 将被注入到构造方法的一个普通结果id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
result
– 注入到字段或 JavaBean 属性的普通结果
association
一个复杂类型的关联;许多结果将包装成这种类型
resultMap
元素,或是对其它结果映射的引用collection
一个复杂类型的集合
resultMap
元素,或是对其它结果映射的引用属性 | 描述 |
---|---|
property |
映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。 |
column |
数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 |
javaType |
一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
场景:一个订单中 订单信息 + User信息
创建订单实体类
@Data
public class Order {
private Integer id;
private String number;
private Date createtime;
private String note;
private User user;
}
创建接口
public interface OrderMapper {
Order selectOrderById(Integer id);
}
mapper.xml
配置
<resultMap id="orderMap" type="order">
<id property="id" column="id"/>
<result property="number" column="number"/>
<result property="createtime" column="createtime" />
<result property="note" column="note"/>
<association property="user" javaType="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
association>
resultMap>
<select id="selectOrderById" parameterType="int" resultMap="orderMap">
select
o.id,o.number,o.createtime,o.note,
u.id,u.username,u.sex,u.address
from mybatis.order as o join user as u on user_id = u.id
where o.id = #{id}
select>
测试
@Test
public void selectOrderById(){
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
OrderMapper mapper = session.getMapper(OrderMapper.class);
//调用方法
Order order = mapper.selectOrderById(3);
//提交事务
session.commit();
//输出结果集
System.out.println(order);
//关闭连接
session.close();
}
结果
<== Columns: id, number, createtime, note, id, username, sex, address
<== Row: 3, 1000010, 2015-02-04 13:22:35.0, null, 1, CodeYuan-Y, 2, null
<== Total:
场景 查询一个用户所有的订单信息
接口
List<User> selectOrderByUserId(Integer id);
mapper.xml
配置
<resultMap id="userMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="orderList" javaType="list" ofType="order">
<id property="id" column="id"/>
<result property="number" column="number"/>
<result property="note" column="note"/>
collection>
resultMap>
<select id="selectOrderByUserId" parameterType="Integer" resultMap="userMap">
select
u.id,u.username,u.sex,u.address,
o.id,o.number,o.note
from mybatis.order as o,user as u
where o.user_id = u.id and u.id = #{id}
select>
测试
@Test
public void selectOrderByUserId(){
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
OrderMapper mapper = session.getMapper(OrderMapper.class);
//调用方法
List<User> list = mapper.selectOrderByUserId(1);
//提交事务
session.commit();
//输出结果集
System.out.println(list);
//关闭连接
session.close();
}
结果
==> Preparing: select u.id,u.username,u.sex,u.address, o.id,o.number,o.note from mybatis.order as o,user as u where o.user_id = u.id and u.id = ?
==> Parameters: 1(Integer)
<== Columns: id, username, sex, address, id, number, note
<== Row: 1, CodeYuan-Y, 2, null, 3, 1000010, 铅笔
<== Row: 1, CodeYuan-Y, 2, null, 4, 1000011, 钢笔
<== Total: 2
多对多、多对多对多…只要你想对下去一定可以,它的实现原理主要是collection
的嵌套又嵌套
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
场景:查询时,如果输入了某些内容就模糊查询,如果没输入就查询全部
接口
List<User> selectUserList(String str);
sql语句
<select id="selectUserList" parameterType="string" resultType="user">
select * from user where 1 = 1
<if test="username != null">
and username like #{username}
if>
select>
------------------或------------------
<select id="selectUserList" parameterType="string" resultType="user">
select * from user
<where>
<if test="username != null">
and username like #{username}
if>
where>
select>
测试
@Test
public void selectUserList(){
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper = session.getMapper(UserMapper.class);
//调用方法
List<User> list = mapper.selectUserList("%张%");
//提交事务
session.commit();
//输出结果集
System.out.println(list);
//关闭连接
session.close();
}
结果
==> Preparing: select * from user where 1 = 1 and username like ?
==> Parameters: %张%(String)
<== Columns: id, username, birthday, sex, address
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Row: 24, 张三丰, null, 1, 河南郑州
<== Total: 3
接口
List<User> selectUserList(User user);
sql语句
<select id="selectUserList" parameterType="user" resultType="user">
select * from user
<where>
<choose>
<when test="username != null">
username like #{username}
when>
<when test="sex != null">
sex = '男'
when>
<otherwise>
id = 1
otherwise>
choose>
where>
select>
when
- when
- otherwise
的结构 相当于 if
- else if
- else
测试
@Test
public void selectUserList(){
User user = new User();
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper = session.getMapper(UserMapper.class);
//调用方法
List<User> list = mapper.selectUserList(user);
//提交事务
session.commit();
//输出结果集
System.out.println(list);
//关闭连接
session.close();
}
结果
==> Preparing: select * from user WHERE id = 1
==> Parameters:
<== Columns: id, username, birthday, sex, address
<== Row: 1, CodeYuan-Y, null, 2, null
<== Total: 1
set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
接口
Integer updateUserUseSet(User user);
sql语句
<update id="updateUserUseSet" parameterType="user">
update user
<set>
<if test="username != null">username = #{username},if>
<if test="sex != null">sex = #{sex},if>
address = #{address}
set>
where id = #{id}
update>
测试
@Test
public void updateUserUseSet(){
User user = new User();
user.setId(1);
user.setSex("男");
user.setAddress("山东青岛");
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper = session.getMapper(UserMapper.class);
//调用方法
mapper.updateUserUseSet(user);
//提交事务
session.commit();
//关闭连接
session.close();
}
结果
==> Preparing: update user SET sex = ?, address = ? where id = ?
==> Parameters: 男(String), 山东青岛(String), 1(Integer)
<== Updates: 1
**item:**集合中元素迭代时的别名,该参数为必选。
index:在list和数组中,index是元素的序号,在map中,index是元素的key,该参数可选
open:foreach代码的开始符号,一般是(和close=")"合用。常用在in(),values()时。该参数可选
separator:元素之间的分隔符,例如在in()的时候,separator=","会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选。
close: select * from user where id in #{item} foreach代码的关闭符号,一般是)和open="("合用。常用在in(),values()时。该参数可选。
collection: 要做foreach的对象,作为入参时,List对象默认用"list"代替作为键,数组对象有"array"代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param(“keyName”)来设置键,设置keyName后,list,array将会失效。 除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List ids。入参是User对象,那么这个collection = “ids”.如果User有属性Ids ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = "ids.id"
接口
List<User> selectForeachById(List<Integer> list);
sql语句
<select id="selectForeachById" parameterType="list" resultType="user">
select * from user where id in
<foreach collection="list" item="item" open=" (" separator="," close=")">
#{item}
foreach>
select>
测试
@Test
public void selectForeachById(){
List<Integer> num = new ArrayList<Integer>();
num.add(1);
num.add(10);
num.add(16);
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper = session.getMapper(UserMapper.class);
//调用方法
List<User> list = mapper.selectForeachById(num);
//输出结果集
for (User u : list) {
System.out.println(u);
}
//关闭连接
session.close();
}
结果
==> Preparing: select * from user where id in ( ? , ? , ? )
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<== Columns: id, username, birthday, sex, address
<== Row: 1, CodeYuan-Y, null, 男, 山东青岛
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Total: 3
Mybatis 使用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache)。
每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 session 执行过的查询结果都会被保存在本地缓存中,所以,当再次执行参数相同的相同查询时,就不需要实际查询数据库了。本地缓存将会在做出修改、事务提交或回滚,以及关闭 session 时清空。
默认情况下,本地缓存数据的生命周期等同于整个 session 的周期。由于缓存会被用来解决循环引用问题和加快重复嵌套查询的速度,所以无法将其完全禁用。但是你可以通过设置 localCacheScope=STATEMENT 来只在语句执行时使用缓存。
你可以随时调用以下方法来清空本地缓存:
void clearCache()
确保 SqlSession 被关闭
void close()
以上面代码为例:
@Test
public void selectForeachById(){
List<Integer> num = new ArrayList<Integer>();
num.add(1);
num.add(10);
num.add(16);
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper = session.getMapper(UserMapper.class);
//调用方法
List<User> list = mapper.selectForeachById(num);
System.out.println("======================================");
List<User> lis = mapper.selectForeachById(num);
//比较
System.out.println(lis.equals(list));
//关闭连接
session.close();
}
我们在调用一个方法查询一段内容后,再次调用查看日志发现,系统只进行了一次sql查询,那么另一次调用的方法干什么了呢?它是对缓存进行了查询,而且查询的内容相同。
==> Preparing: select * from user where id in ( ? , ? , ? )
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<== Columns: id, username, birthday, sex, address
<== Row: 1, CodeYuan-Y, null, 男, 山东青岛
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Total: 3
======================================
true
当我们在调用第一次查询方法后,清除缓存,然后再次查询会发现。
@Test
public void selectForeachById(){
List<Integer> num = new ArrayList<Integer>();
num.add(1);
num.add(10);
num.add(16);
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper = session.getMapper(UserMapper.class);
//调用方法
List<User> list = mapper.selectForeachById(num);
session.clearCache();
System.out.println("===================清除缓存后===================");
List<User> lis = mapper.selectForeachById(num);
//比较
System.out.println(lis.equals(list));
//关闭连接
session.close();
}
==> Preparing: select * from user where id in ( ? , ? , ? )
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<== Columns: id, username, birthday, sex, address
<== Row: 1, CodeYuan-Y, null, 男, 山东青岛
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Total: 3
===================清除缓存后===================
==> Preparing: select * from user where id in ( ? , ? , ? )
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<== Columns: id, username, birthday, sex, address
<== Row: 1, CodeYuan-Y, null, 男, 山东青岛
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Total: 3
true
由于缓存被清除了,所有系统进行了两次查询。
同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;
不同的 SqlSession 之间的缓存是相互隔离的
一级缓存只在同一个SqlSession使用范围内有效
用一个 SqlSession, 可以通过配置使得在查询前清空缓存;
任何的 UPDATE, INSERT, DELETE 语句都会清空缓存。
由于内容发生了变化,缓存的内容与数据库中的不相符,所以没有存在的意义了
在...mapper.xml
加以下标签,需要注意的是Mybatis
使用二级缓存,实体类必须进行序列化。至于为什么要序列化?是由于缓存的key-value
的存储特性。
<cache/>
测试
@Test
public void selectForeachById(){
List<Integer> num = new ArrayList<Integer>();
num.add(1);
num.add(10);
num.add(16);
//获取SqlSession对象
SqlSession session= MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper1 = session.getMapper(UserMapper.class);
//调用方法
List<User> list = mapper1.selectForeachById(num);
//关闭连接
session.close();
System.out.println("===================重启SqlSession===================");
//获取SqlSession对象
session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper2 = session.getMapper(UserMapper.class);
//调用方法
List<User> lis = mapper2.selectForeachById(num);
//关闭连接
session.close();
}
结果
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@c7b4de]
==> Preparing: select * from user where id in ( ? , ? , ? )
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<== Columns: id, username, birthday, sex, address
<== Row: 1, CodeYuan-Y, null, 男, 山东青岛
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Total: 3
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@c7b4de]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@c7b4de]
Returned connection 13087966 to pool.
===================重启SqlSession===================
Cache Hit Ratio [com.yy.mapper.UserMapper]: 0.5
基本上就是这样。这个简单语句的效果如下:
提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新
配置映射文件
<mappers>
<mapper class="com.yy.mapper.UserMapper" />
mappers>
注解
@Select("select * from user where 1 = 1")
List<User> getUserList();
更多注解使用方法
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。
SqlSessionFactoryBuilder
创建SqlSessionFactory
。SqlSessionFactory
创建SqlSession
。SqlSession
拿到Mapper
对象的代理。MapperProxy
调用Maper
中相应的方法。(1)读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。
(2)加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
(3)构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。
(4)创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
(5)Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
(6)MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
(7)输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
(8)输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。