动力节点_Mybatis
适合配套学习的教程:
Mybatis讲义.doc,连接:https://shimo.im/docx/m5kv9dp2XgcDGaqX/
MyBatis-3-User-Guide-Simplified-Chinese.pdf,连接:https://shimo.im/files/8Nk6MeN8gJf99nqL/
1. 什么是SSM.
- Spring:它是整合其它框架的框架,它的核心是IOC和AOP,它由20多个模块构成。在很多领域都提供了很好的解决方案,是一个大佬级别的存在;
- SpringMVC:它是Spring家族的一员,专门用来优化控制器(Servlet)的,提供了极简单数据提交,数据携带,页面跳转等功能;
- MyBatis:是持久化层的一个框架,用来进行数据库访问的优化,专注于sql语句。极大的简化了JDBC的访问;
什么是框架:
它是一个半成品软件,将所有公共的,重复的代码解决掉,帮助程序快速高效的进行开发。它是可复用,可扩展的。
1.1 什么是MyBatis框架
MyBatis 本是 apache 的一个开源项目iBatis, 2010 年这个项目由 apache software foundation 迁移到了 google code,并且改名为MyBatis 。2013 年 11 月迁移到 Github,最新版本是 MyBatis 3.5.7 ,其发布时间是 2021 年 4月 7日。MyBatis完成数据访问层的优化,它专注于sql语句,简化了过去JDBC繁琐的访问机制。
添加框架的步骤:
- 添加依赖
- 添加配置文件
1.2 入门案例
-
新建库建表
#创建数据库ssm CREATE DATABASE ssm DEFAULT CHARSET utf8; #使用(打开)ssm数据库 use ssm; #创建表student CREATE TABLE `student` ( `id` int(11) AUTO_INCREMENT primary key , `name` varchar(255) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into student(name,email,age) values('张三','zhangsan@126.com',22); insert into student(name,email,age) values('李四','lisi@126.com',21); insert into student(name,email,age) values('王五','wangwu@163.com',22); insert into student(name,email,age) values('赵六','zhaoliun@qq.com',24); select * from student;
-
新建maven项目,选quickstart模板
-
修改目录,添加缺失的目录,修改目录属性。这里主要是resources文件;
-
修改pom.xml文件,添加MyBatis的依赖,添加mysql的依赖;
<!--Mybatis依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!--MySQL依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.3</version> </dependency>
-
修改pom.xml文件,添加资源文件指定
<build> <resources> <resource> <directory>src/main/java</directory><!--扫描Java目录下的所有指定文件--> <includes><!--包括目录下的.properties,.xml 文件都会扫描到--> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory><!--所在的目录--> <includes><!--包括目录下的.properties,.xml 文件都会扫描到--> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
-
在idea中添加数据库的可视化,主要是方便数据管理
-
添加jdbc.properties属性文件(数据库的配置)
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=root
-
添加SqlMapConfig.xml文件,MyBatis的核心配置文件
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> </configuration>
加载数据库连接配置信息:
resource:会自动到资源目录resources下去扫描指定的文件;
url:这里指定的是绝对地址;<properties resource="jdbc.properties"/>
配置环境:
<!--environments 的default属性指定以下的多个environment中那一个environment生效。--> <environments default="development"> <!--注意:这里的environment可以拥有多一个,不同的environment之间使用不同的id进行区分。--> <environment id="development"> <!-- transactionManager的type属性指定事务的管理类型,需要使用大写: JDBC,由程序员自行控制事物控制。 MANAGED,有容器进行事物的控制。 --> <transactionManager type="JDBC"> </transactionManager> <!-- dataSource的type属性指定数据源的类型,需要使用大写: JNDI:java命名目录接口,在服务器端进行数据库连接池的管理; POOLED:使用数据库连接池; UNPOOLED:不使用数据库连接池; --> <dataSource type="POOLED"> <!-- 数据库连接池的基本配置,底层源码: private String driver; private String url; private String username; private String password; --> <property name="driver" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments>
注册mapper.xml文件
<!--注册mapper.xml文件--> <!-- resources:从resource目录下找指定名称的文件进行注册; url:使用绝对路径进行注册; class:动态代理方式下的注册; --> <mappers> <mapper resource="StudentMapper.xml"/> </mappers>
-
创建实体类Student,用来封装数据
public class Student { private Integer id; private String name; private String email; private Integer age; // getter setter 有参无参构造函数 toString... }
-
添加完成学生表的增删改查的功能的StudentMapper.xml文件
mapper文件头:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
mapper标签:
<!-- mapper:是整个文件的大标签,用来开始和结束xml文件,其属性有:namespace,指定命名空间(相当于包名),用来区分不同mapper.xml文件中相同的ID属性。 --> <mapper namespace="student"> </mapper>
查询所有的学生信息:
<!-- 完成查询全部学生的功能: 以前使用JDBC的方式为:List<Student> getAll(); 现在使用Mybatis后,方式变得很简单,只需要配置属性即可: id: 用来区分不同的的标签,相当于方法名; resultType:指定查询返回的结果集的类型,如果是集合,则必须为该集合指定泛型,且必须是包全名。 parameterType:如果有参数,则通过它来指定参数的类型; --> <select id="getAll" resultType="com.xuan.pojo.Student"> select id, name, email, age from student; </select>
按主键id查询学生信息
<!-- Student getById(Integer id); --> <select id="getById" parameterType="int" resultType="com.xuan.pojo.Student"> select id, name, email, age from student where id = #{id} </select>
按学生姓名进行查询
<!-- List<Student> getByName(String name); --> <select id="getByName" parameterType="string" resultType="com.xuan.pojo.Student"> select id, name, email, age from student where name like #{name} </select>
添加学生信息
Mybatis中,增删改在xml文件中是没有执行的返回结果(即,可以不写其返回值),可以通过SqlSession对象的相关方法获取执行结果。
<!-- insert(Student student); Student POJO: private String name; private String email; private Integer age; 由于这里的参数类型是Student类型,在下面的values中,只需要按照需要写上Student类型的属性名即可,然后在调用方法时会自动为该参数赋值。 --> <insert id="insert" parameterType="com.xuan.pojo.Student"> insert into student(name, email, age) values (#{name}, #{email}, #{age}) </insert>
删除学生
<!-- delete(Integer id); --> <delete id="delete" parameterType="int"> delete from student where id = #{id} </delete>
删除学生
<!-- update(Integer id); --> <update id="update" parameterType="com.xuan.pojo.Student"> update student set name = #{name}, email = #{email}, age = #{age} where id = #{id} </update>
-
创建测试类,进行功能测试
public class MyTest { // 优化测试 SqlSession sqlSession; /* @Before注解:在所有被@Test注解标注的方法执行之前执行被@Before注解标注的方法里面的代码,注解@After则相反。 该方法实现:读取Mybatis核心配置文件、创建SqlSessionFactory、创建SqlSession对象 */ @Before public void getSqlSession() throws IOException { // 使用文件流读取核心配置文件:SqlMapConfig.xml InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); // 创建SqlSessionFactory工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); // 取出sqlSession对象 sqlSession = factory.openSession(); } /** * 该方法用来关闭SqlSession对象。 * 每次执行被@Test注解标注的方法在执行完毕后都会执行该方法。 */ @After public void closeSqlSession(){ // 关闭sqlSession,实际上是将该链接放回到连接池 sqlSession.close(); } @Test public void testAll() throws IOException { // 完成查询操作 List<Student> studentList = sqlSession.selectList("student.getAll"); studentList.forEach(System.out::println); } @Test public void testSpecificStudent() throws IOException { Student student = sqlSession.selectOne("student.getById", 1); System.out.println(student); } @Test public void testSpecificName() throws IOException { List<Student> studentList = sqlSession.selectList("student.getByName", "%三%"); studentList.forEach(System.out::println); } @Test public void testInsertAndDelAndUpdate() throws IOException { // 添加 //sqlSession.insert("student.insert", new Student(1111, "hh", "hh@hhh.com", 20)); // 修改 // sqlSession.update("student.update", new Student(5, "babql", "babq@hhh.com", 222)); // 删除 sqlSession.delete("student.delete", 5); // 切记这里要手动提交事务 sqlSession.commit(); } }
1.3 MyBatis对象分析
Resources类
就是解析SqlMapConfig.xml文件,创建出相应的对象,代码如下:
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory接口
使用ctrl+h快捷键查看本接口的子接口及实现类:
这里可以看到,DefaultSqlSessionFactory是SqlSessionFactory接口的实现类,通过new SqlSessionFactoryBuilder().build(in)这种方式创建SqlSession对象时,底层使用的就是DefaultSqlSessionFactory实现类。
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
SqlSession接口
DefaultSqlSession是SqlSession主要的实现实现类。SqlSession中的很多方法都是在DefaultSqlSession中进行实现的。
1.4 补充
为实体类注册别名:
<!--为实体类注册别名-->
<typeAliases>
<!--为单个类注册别名-->
<!--<typeAlias type="com.xuan.pojo.Student" alias="student"/>-->
<!--
使用批量注册别名(推荐使用):
这里会为指定包下的所有类取别名,规则为类名首字母小写的驼峰命名法。
-->
<package name="com.xuan.pojo"/>
</typeAliases>
设置日志输出:
<!--设置日志输出底层执行的sql语句-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
2. 动态代理存在意义
在三层架构中,业务逻辑层要通过接口访问数据层的功能,但是这里有个问题,就是业务逻辑层不能直接方法DAO层xml文件中的方法(功能),所以这里使用到了动态代理来解决这也问题。
2.1 动态代理的实现规范
- UsersMapper.xml文件与UsersMapper.java的接口必须同一个目录下;
- UsersMapper.xml文件与UsersMapper.java的接口的文件名必须一致,后缀不一样;
- UserMapper.xml文件中标签的id值与UserMapper.java的接口中方法的名称完全一致;
- UserMapper.xml文件中标签的parameterType属性值与UserMapper.java的接口中方法的参数类型完全一致;
- UserMapper.xml文件中标签的resultType值与UserMapper.java的接口中方法的返回值类型完全一致;
- UserMapper.xml文件中namespace属性必须是接口的完全限定名称com.bjpowernode.mapper.UsersMapper;
- 在SqlMapConfig.xml文件中注册mapper文件时,使用class=接口的完全限定名称com.bjpowernode.mapper.UsersMapper;
2.2 动态代理访问的步骤
- 建表Users;
- 新建maven工程,刷新可视化;
- 修改目录;
- 修改pom.xml文件,添加依赖;
- 添加jdbc.propertis文件到resources目录下;
- 添加SqlMapConfig.xml文件;
- 添加实体类;
- 添加mapper文件夹,新建UsersMapper接口;
- 在mapper文件夹下,新建UsersMapper.xml文件,完成增删改查功能;
- 添加测试类,测试功能;
12.优化mapper.xml文件注册;
Users数据库表:
use ssm;
-- ----------------------------
-- Table structure for `users`
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) COMMENT '用户名称',
`birthday` date DEFAULT NULL COMMENT '生日',
`sex` char(2) DEFAULT NULL COMMENT '性别',
`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `users` VALUES (1, '王五', '2000-09-10', '2', '安徽');
INSERT INTO `users` VALUES (2, '张三', '2001-07-12', '1', '北京市');
INSERT INTO `users` VALUES (3, '张小明', '1999-02-22', '1', '河南');
INSERT INTO `users` VALUES (4, '陈小亮', '2002-11-19', '1', '辽宁');
INSERT INTO `users` VALUES (5, '张三丰', '2001-03-10', '1', '上海市');
INSERT INTO `users` VALUES (6, '陈小明', '2002-01-19', '1', '重庆市');
INSERT INTO `users` VALUES (7, '王五四', '2001-05-13', '2', '天津市');
select * from users;
注册mapper.xml文件
<!--注册mapper.xml文件-->
<mappers>
<!--绝对路径注册-->
<mapper url="/"></mapper>
<!--非动态代理方式下的注册-->
<mapper resource="StudentMapper.xml"></mapper>
<!--动态代理方式下的单个mapper.xml文件注册-->
<mapper class="com.bjpowernode.mapper.UsersMapper"></mapper>
<!--批量注册(推荐使用)-->
<package name="com.bjpowernode.mapper"></package>
</mappers>
测试类
public class MyTest {
SqlSession sqlSession;
// Mybatis动态代理出来的对象
UserMapper userMapper;
// 格式化时间对象
SimpleDateFormat spf;
@Before
public void getSqlSession() throws IOException {
// 加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 获取工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//获取SqlSession对象
sqlSession = factory.openSession();
// 取出动态代理的对象,完成接口中方法的调用,实则是调用xml文件中相对应标签的功能
userMapper = sqlSession.getMapper(UserMapper.class);
// 格式化时间对象,年月日
spf = new SimpleDateFormat("yyyy-MM-dd");
}
}
2.3 #{}与${}
#{}占位符:
传参大部分使用#{}传参,它的底层使用的是PreparedStatement对象,是安全的数据库访问 ,可以有效的防止sql注入问题。#{}里如何写,这里主要是看parameterType参数的类型:
-
如果parameterType的类型是Java基本数据类型(或其封装类)与String,则#{}里面可以随便写;
<!-- User getUserByUsername(String username); --> <select id="getUserByUsername" resultType="user" parameterType="string"> select id, username, birthday, sex, address from users where username like #{username} </select>
-
如果parameterType的类型是实体类的类型,则#{}里只能是类中成员变量的名称,而且区分大小写;
<!-- 更新用户 在xml文件中增删改不需要写返回值。 User实体类: private Integer id; private String username; private Date birthday; private String sex; private String address; --> <update id="update" parameterType="user"> update users set username = #{username}, birthday = #{birthday}, sex = #{sex}, address = #{address} where id = #{id} </update>
${}: 通常用于字符串拼接或字符串替换,具体如下:
-
字符串拼接,一般用于模糊查询中.建议少用,因为有sql注入的风险。 也分两种情况,同样的看parameterType的类型:
- A. 如果parameterType的类型是简单类型,则${}里随便写,但是分版本,如果是3.5.1及以下的版本,只能写value;
```xml <select id="getByName" parameterType="string" resultType="users"> ===>入参是简单类型 select id,username,birthday,sex,address from users where username like '%${zar}%' ===>随便写 </select> ```
-
如果parameterType的类型是实体类的类型,则${}里只能是类中成员变量的名称(现在已经少用);
-
优化后的模糊查询(以后都要使用这种方式);
<!-- List<User> getUserByUsernameBetter(String username); 这种貌似更专业一点。但是本质上还是字符串的拼接,可以有效抵御SQL注入问题。 --> <select id="getUserByUsername" resultType="user" parameterType="string"> select id, username, birthday, sex, address from users where username like concat('%', #{username}, '%') </select>
-
字符串替换(使用的还有点多)
需求:要求模糊查询地址或用户名;
select * from users where username like '%小%'; select * from users where address like '%市%';
这里如果是按照上面的方式写sql,那么可能就要对应两个不同的方法,所以这里可以使用替换的方式使用一个方法进行实现。
/**
* 通过列名与列值获取对应的用户信息。这里@Param参数value可以与被修饰的参数名不一样,但是规范要求一样。
* @param columnName 对应表的列的名称,为了能够在xml文件中正确的取到该值,这两个参数必须使用@Param注解进行修饰。
* @param columnValue 对应表的列的值。
* @return 返回查询的用户的信息。
*/
List<User> getUserByUsernameOrAddr(
@Param("columnName")
String columnName,
@Param("columnValue")
String columnValue
);
<!--
List<User> getUserByUsernameOrAddr(
@Param("columnName")
String columnName,
@Param("columnValue")
String columnValue);
当参数有多个时,可以不写。
注意:这里的列名必须使用${}进行接收,在预编译sql语句的时候才能够将对应的列名解释出来:
where ${columnName} like concat('%', #{columnValue}, '%')
-->
<select id="getUserByUsernameOrAddr" resultType="user">
select id, username, birthday, sex, address
from users
where ${columnName} like concat('%', #{columnValue}, '%')
</select>
2.4 返回主键值
业务描述:当新增一个用户之后,给用户id积分自动加500。
这里涉及一个问题,用户表中的uid字段是自增,当添加用户时,用户的id是自动生成的,那么这里在新增一个用户后,如何同时获取到自动生成的uid给用户在积分表中添加积分值?这里可以使用mysql中的一个函数last_insert_id(),该函数会自动返回最新插入到表中的数据行对应的id。下面来解决这个问题。
在插入语句结束后, 返回自增的主键值到入参的users对象的id属性中。
/**
* 新增用户,添加之后返回用户的id。
* @param user 添加的对象;
* @return 返回影响的行数;
*/
int insert(User user);
<!--
int insert(User user);
keyProperty:使用user对象的id属性来接收返回的主键值;
resultType:返回的主键值的类型;
order:在插入语句执行之前还是之后返回主键的值;
-->
<insert id="insert" parameterType="user">
<selectKey keyProperty="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into users(username, birthday, sex, address) values (#{username}, #{birthday}, #{sex}, #{address})
</insert>
@Test
public void testInsert() throws ParseException {
User user = new User(11, "fjaidfdasfad", spf.parse("1999-02-03"), "2", "广东深圳");
userMapper.insert(user);
sqlSession.commit();
System.out.println(user);
}
2.4 UUID
这是一个全球唯一随机字符串,由36个十六进制数以及中划线组成。
// UUID
@Test
public void testUUID(){
UUID uuid = UUID.randomUUID();
System.out.println(uuid.toString().replace("-", ""));
}
3. 动态sql
可以定义代码片断,进行逻辑判断,循环处理(批量处理),使条件判断更为简单;
3.1 代码片段
-
:用来定义代码片断,可以将所有的列名,或复杂的条件定义为代码片断,供使用时调用,实现sql的重复使用;
<!--定义代码片段--> <sql id="allColumns"> id, username, birthday, sex, address </sql>
-
:用来引用定义的代码片断;
<select id="allUsers" resultType="user"> select <include refid="allColumns"/> from users </select>
3.2 逻辑判断
-
:进行条件判断;
test条件判断的取值可以是实体类的成员变量,可以是map的key,可以是@Param注解的名称
-
:进行多条件拼接,在查询,删除,更新中使用;
/** * 进行多条件查询。 * @param user 传入一个对象,判断对象是否为null,如果不为空,则该属性值即为查询条件之一。 * @return 查询的对象; */ List<User> moreConditionQuery(User user);
<!-- 使用动态sql进行多条件查询: List<User> moreConditionQuery(User user); 提示:字符串类型的字段需要判断null和'',而日期类型的字段只需要判断是否为null即可。 select id, username, birthday, sex, address from users where 1 = 1 // 动态sql中<where></where>标签的本质就是相当于这里1=1的占位符。 and username like '%陈%' and sex = '1'; --> <select id="moreConditionQuery" resultType="user" parameterType="user"> select <include refid="allColumns"/> from users <where> <if test="username != null and username != ''"> and username like concat('%', #{username}, '%') </if> <if test="birthday != null"> and birthday = #{birthday} </if> <if test="sex != null"> and sex = #{sex} </if> <if test="address != null and address != ''"> and address like concat('%', #{address}, '%') </if> </where> </select>
// 多条件查询 @Test public void testMoreConditionQuery(){ // 默认为查询所有的用户 User user = new User(); // 查询用户名中包含“小”的用户 user.setUsername("小"); // 并且查询所在地址中包含“市”的字 user.setAddress("市"); List<User> userList = userMapper.moreConditionQuery(user); userList.forEach(System.out::println); }
底层执行的sql语句分析,以上的sql使用了与标签进行了条件的判断。当查询的用户对象所有的属性全为空时,底层执行的sql为:
当用户属性有一个不为空时,这里以用户名为小作为查询条件。执行的sql为:
这里有个问题值得注意,就是我们在xml文件中,已经写了当用户名不为空时将用户名加入查询的条件:
<if test="username != null and username != ''"> and username like concat('%', #{username}, '%') </if>
可以看到用户名前面的and已经被丢弃了,所以,当以多个参数作为条件时,其中如果有一个参数不为空,则会为该sql自动加上where条件,并且第一个不为空的参数前面的and会被省略。
3.3 更新操作
:有选择的进行更新处理,至少需要更新一列。 能够保证如果没有传值进来,则数据库中的数据保持不变;注意点:前面演示的更新操作中其实都存在问题,即当用户传入的user对象只有username属性不为空,这时如果进行更新操作,其他的字段都会被替换为null。
所以在进行更新操作前需要判断用户出入的属性是否为空,如果为空该字段就不进行更新。
/**
* 选择性的进行更新操作。
* @param user 更新的用户;
* @return 返回影响行数;
*/
int conditionUpdate(User user);
<!--
int conditionUpdate(User user);
增删改的返回结果不用写。
<set>标签:
这里面可以使用<if>标签进行逻辑的判断,在每个属性的赋值后都要加上一个逗号进行收尾。
因为我们不知道哪个属性是最后一个,但是Mybatis是知道的,它会自动进行判断。
如果是最后一个字段,则该字段后面的逗号会自动被删除。
-->
<update id="conditionUpdate" parameterType="user">
update users
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="birthday != null">
birthday = #{birthday},
</if>
<if test="sex != null">
sex = #{sex},
</if>
<if test="address != null and address != ''">
address = #{address},
</if>
</set>
where id = #{id}
</update>
// 选择更新
@Test
public void testConditionUpdate(){
User user = new User();
user.setId(9);
user.setUsername("张宇");
userMapper.conditionUpdate(user);
sqlSession.commit();
}
底层执行的sql语句为:
这种方式必须至少更新一列,否则回报一下的错误:
3.4 遍历操作
< foreach>:用来进行循环遍历,完成循环条件查询,批量删除(常用),批量增加(偶尔用),批量更新(很少用);
查询实现:
<!--
List<User> queryMoreUserById(Integer[] ids);
这里由于传入的是数组类型,所以在select标签的属性里面不用写array。
foreach标签个属性:
collection: 固定值为:map、list、array,即看传入的参数是什么类型,这里就选择对应的类型就好了;
item:从容器中取出的每一项的临时变量,命名随意;
separator:取出的每一项之间的分隔符;
open:包裹foreach之前的符号;
close:包裹foreach之后的符号;
-->
<select id="queryMoreUserById" resultType="user">
select <include refid="allColumns"/>
from users
where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
<!--
批量删除
int batchDel(Integer[] ids);
-->
<delete id="batchDel">
delete from users
where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
<!--
// 批量增加
void batchInsert(List<User> users);
由于上面对应方法的参数是List类型,所以<insert>标签中的parameterType属性的值可以不用声明,当然声明也可以(这里需要声明为user)。
对应的sql语句:
insert into users(username, birthday, sex, address) values
('a', '2002-01-02', '2', '花溪a'),
('b', '2002-01-02', '2', '花溪b'),
('c', '2002-01-02', '2', '花溪c');
注意:由于这里的foreach遍历出来的变量是user对象,所以使用对象.属性的方式使用属性值。
-->
<insert id="batchInsert">
insert into users(username, birthday, sex, address) VALUES
<foreach collection="list" separator="," item="user">
(#{user.username}, #{user.birthday}, #{user.sex}, #{user.address})
</foreach>
</insert>
3.5 指定参数位置
如果入参是多个,可以通过指定参数位置进行传参。如果是实体包含不住的条件参数类型,那么就只能使用形参这种方式了。例如:查询指定日期范围内的用户信息。
/*
一个方法有多个参数,且这些参数无法通过一个对象进行封装。这里有两种解决方案(不推荐使用):
1. 使用@Param()注解;
2. 使用#{args0},#{args1}...
*/
// 查询指定日期出生的用户
List<User> queryByBirthday(Date beginDate, Date endDate);
<!--
List<User> queryByBirthday(Date beginDate, Date endDate);
-->
<select id="queryByBirthday" resultType="user">
select <include refid="allColumns"/>
from users
where birthday between #{arg0} and #{arg1}
</select>
@Test
public void testQueryByBirthday() throws ParseException {
Date beginDate = spf.parse("2002-01-01");
Date endDate = spf.parse("2002-12-31");
List<User> users = userMapper.queryByBirthday(beginDate, endDate);
users.forEach(System.out::println);
}
3.6 入参是Map(重点掌握)
如果入参超过一个以上,使用map封装查询条件,更有语义,查询条件更明确。
// 使用Map对上面的方法进行优化(推荐使用,也是应该重点掌握的)
List<User> queryByBirthdayMap(Map<String, Date> map);
<!--
List<User> queryByBirthdayMap(Map<String, Date> map);
这里使用Map时,直接使用#{key}的方式即可将Map中的数据取出;
-->
<select id="queryByBirthdayMap" resultType="user">
select <include refid="allColumns"/>
from users
where birthday between #{beginDate} and #{endDate}
</select>
@Test
public void testQueryByBirthdayMap() throws ParseException {
Map<String, Date> map = new HashMap<>();
Date beginDate = spf.parse("2002-01-01");
Date endDate = spf.parse("2002-12-31");
map.put("beginDate", beginDate);
map.put("endDate", endDate);
List<User> users = userMapper.queryByBirthdayMap(map);
users.forEach(System.out::println);
}
3.7 返回值是Map
如果返回的数据实体类无法包含,可以使用Map返回多张表中的若干数据.返回后这些数据之间没有任何的内在联系。就是Object类型,返回的Map的key就是查询查询sql语句的列名或别名。
<!--
只返回一个结果的情况。
Map<Object, Object> queryReturnMap(Integer id);
-->
<select id="queryReturnMap" resultType="map" parameterType="int">
select username, address
from users
where id = #{id}
</select>
<!--
List<Map<Object, Object>> queryMulMap();
-->
<select id="queryMulMap" resultType="map">
select username, address
from users
</select>
3.8 解决列名不一致的问题
sql代码:
USE ssm;
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`bookid` INT(11) NOT NULL AUTO_INCREMENT,
`bookname` VARCHAR(32) NOT NULL COMMENT '图书名称',
PRIMARY KEY (`bookid`)
) ENGINE=INNODB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8;
INSERT INTO `book` VALUES (1, 'java基础');
INSERT INTO `book` VALUES (2, 'sql基础');
SELECT * FROM book;
当实体类的属性与表中的字段名不一致的时候,这是无法直接对数据进行封装,通常有以下的解决方案:
- 写sql的时候通过给sql取别名的方式让实体类的属性名与这个别名一样;
- 使用resultMap手工绑定的方式解决;
a>方案一
public class Book {
private Integer id;
private String name;
// ... gettter and setter and constructor and toString()
}
<!--
// 获取所有的
List<Book> allBook();
这里给sql语句取别名。
-->
<select id="allBook" resultType="book">
select bookid id, bookname name from book
</select>
b>方案二(推荐使用)
<!--
使用resultMap手工完成映射。
resultMap的type属性:表示需要进行映射的类的属性,可以理解为需要返回的类型。
property:类中的属性名称,区分大小写;
column:表中的列名,不区分大小写;
-->
<resultMap id="bookMap" type="book">
<!--主键绑定:主键的专属标签-->
<id property="id" column="bookid"/>
<!--非主键绑定-->
<result property="name" column="bookname"/>
</resultMap>
<select id="allBook" resultMap="bookMap">
select bookid, bookname from book
</select>
4. 表之间的关联关系
关联关系是有方向的。
- 一对多关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在老师方,就是一对多关联。
- 多对一关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在学生方,就是多对一关联。
- 一对一关联:一个老师辅导一个学生,一个学生只请教一个老师.学生和老师是一对一。
- 多对多关联:园区划线的车位和园区的每一辆车,任意一个车位可以停任意一辆车,任意一车辆车可以停在任意一个车位上。
4.1 一对多关联关系
sql代码:
USE ssm;
CREATE TABLE customer(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32),
age INT2
);
INSERT INTO customer VALUES(1,'张三',22);
INSERT INTO customer VALUES(2,'李四',23);
INSERT INTO customer VALUES(3,'王五',24);
CREATE TABLE orders(
id INT PRIMARY KEY AUTO_INCREMENT,
orderNumber VARCHAR(16),
orderPrice DOUBLE,
customer_id INT
);
INSERT INTO orders VALUES(11,20,22.22,1);
INSERT INTO orders VALUES(12,60,16.66,1);
INSERT INTO orders VALUES(13,90,19.99,2);
SELECT * FROM customer;
SELECT * FROM orders;
客户和订单就是典型的一对多关联关系,一个客户名下可以有多个订单。客户表是一方,订单表是多方,通常一个客户表中持有订单的集合,使用一对多的关联关系,可以满足查询客户的同时查询该客户名下的所有订单。
<!--
// 通过用户id查询指定用户以及该用户所有的订单
Customer allCustomer(Integer id);
Customer实体类:
private Integer id;
private String name;
private short age;
// 一个客户对应手中有多个订单
private List<Orders> ordersList;
-->
<select id="allCustomer" resultMap="customerMap" parameterType="int">
select c.id cid, name, age, o.id, ordernumber, orderprice, customer_id
from customer c left join orders o on c.id = o.customer_id
where c.id = #{id};
</select>
<!--这里的type值可以理解为返回的对象的类型。-->
<resultMap id="customerMap" type="customer">
<!--主键-->
<id property="id" column="cid"/>
<!--非主键-->
<result property="name" column="name"/>
<result property="age" column="age"/>
<!--
特殊类型(难点),对象属性是集合。
private Integer id;
private String orderNumber;
private Double orderPrice;
-->
<collection property="ordersList" ofType="orders">
<!--主键-->
<id property="id" column="oid"/>
<!--非主键-->
<result property="orderNumber" column="orderNumber"/>
<result property="orderPrice" column="orderPrice"/>
</collection>
</resultMap>
4.2 多对一关联关系.
订单和客户就是多对一的关联,站在订单的方向查询订单的同时将客户信息查出。订单是多方,持有一方的对象,客户是一方。
package com.xuan.mapper;
import com.xuan.pojo.Orders;
public interface OrdersMapper {
// 查询指定的订单包括下单的用户,注:为了简单起见,这里的用户只有一个,即:订单与用户为多对一的关系。
Orders getOrderById(Integer id);
}
<mapper namespace="com.xuan.mapper.OrdersMapper">
<!--
Orders getOrderById(Integer id);
多表查询,查询每个订单对应的用户。
注意:这里是多对一的情况,即订单和客户的关系。这里查询的sql要注意一个问题,就是订单不会无中生有,一定是有客户下单了才会产生订单,
即:有订单的前提是一定有对应的用户存在。所以,在以下的sql中只能写内连接后右连接。
-->
<select id="getOrderById" parameterType="int" resultMap="orderMap">
select o.id oid, ordernumber, orderprice, customer_id, c.id cid, name, age
from orders o inner join customer c on c.id = o.customer_id
where o.id = #{id};
</select>
<!--
Orders实体类:
private Integer id;
private String orderNumber;
private Double orderPrice;
// 多的一方持有一方的对象
private Customer customer;
-->
<resultMap id="orderMap" type="orders">
<id property="id" column="oid"/>
<result property="orderNumber" column="orderNumber"/>
<result property="orderPrice" column="orderPrice"/>
<!--
处理关联的属性customer
javaType:指定customer属性的参数类型,这里可以使用别名。
Customer实体类:
private Integer id;
private String name;
private short age;
// 在这里这个参数不用指定。
private List<Orders> ordersList;
-->
<association property="customer" javaType="customer">
<id property="id" column="cid"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
</association>
</resultMap>
</mapper>
4.3 一对一关联
一个班级只有一个授课老师,一个老师也只为一个班级授课。
4.4 多对多关联
总结:无论是什么关联关系,如果某方持有另一方的集合,则使用标签完成映射,如果某方持有另一方的对象,则使用标签完成映射。
5. 事务
多个操作同时完成,或同时失败称为事务处理。事务有四个特性(ACID):一致性,持久性,原子性,隔离性。
下订单的业务:
- 订单表中完成增加一条记录的操作;
- 订单明细表中完成N条记录的增加;
- 商品数据更新(减少);
- 购物车中已支付商品删除;
- 用户积分更新(增加);
在MyBatis框架中设置事务:
// 程序员自己控制处理的提交和回滚
<transactionManager type="JDBC"></transactionManager>
可以在创建SqlSession对象的时候设置事务:
sqlSession = factory.openSession(); =>默认是手工提交事务,设置为false也是手工提交事务,如果设置为true,则为自动提交。
sqlSession = factory.openSession(true); =>设置为自动提交,在增删改后不需要commit();
6. 缓存
MyBatis框架提供两级缓存,一级缓存和二级缓存。默认开启一级缓存;缓存就是为了提交查询的效率。
使用缓存后,查询的流程:
查询时先到缓存里查,如果没有则查询数据库,在数据库查到数据后先将该数据放入缓存,再从缓存当中给返回客户端。下次再查询的时候直接从缓存返回,不再访问数据库。如果数据库中发生commit()操作,则缓存会被全部清空。
一级缓存使用的是SqlSession的作用域,同一个sqlSession共享一级缓存的数据。
二级缓存使用的是mapper的作用域,不同的sqlSession只要访问的同一个mapper.xml文件,则共享二级缓存作用域。
6.1 直接从缓存里面取
// 测试缓存
@Test
public void testCache(){
User u1 = userMapper.getUserById(1);
System.out.println("user1: " + u1);
System.out.println("=======================");
User u2 = userMapper.getUserById(1);
System.out.println("user2: " + u2);
// 直接比较内存地址是否相同,如果相同则是同一份数据,否则不是。
System.out.println("user1 == user2 : " + (u1 == u2));
}
6.2 缓存被清空
public void testCache() throws ParseException {
User u1 = userMapper.getUserById(1);
System.out.println("user1: " + u1);
System.out.println("=======================");
// 执行更新操作
userMapper.update(
new User(1, "王二五", spf.parse("2020-01-01"), "2", "花溪区大学城"));
// 执行增删改之后,一定要提交事务
sqlSession.commit();
User u2 = userMapper.getUserById(1);
System.out.println("user2: " + u2);
System.out.println("user1 == user2 : " + (u1 == u2));
}
7. 什么是ORM
ORM(Object Relational Mapping):对象关系映射,MyBatis框架是ORM非常优秀的框架,java语言中以对象的方式操作数据,存到数据库中是以表的方式进行存储,对象中的成员变量与表中的列之间的数据互换称为映射,整个这套操作就是ORM。
持久化的操作:将对象保存到关系型数据库中 ,将关系型数据库中的数据读取出来以对象的形式封装。
MyBatis是持久化层优秀的框架。
8. Mybatis源码分析
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.allUser(); // 待分析