1.数据准备
- 在
mysql
中创建一个名为mybatis
的数据库; - 导入
table.sql
创建数据表(本例中使用的是MySQL5.7
,这个sql
文件其他版本可能不适用)
-- MySQL dump 10.13 Distrib 5.7.17, for Win64 (x86_64)
--
-- Host: localhost Database: mybatis
-- ------------------------------------------------------
-- Server version 5.7.19-log
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `items`
--
DROP TABLE IF EXISTS `items`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`price` float(10,1) NOT NULL,
`detail` text,
`pic` varchar(64) DEFAULT NULL,
`createtime` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `orderdetail`
--
DROP TABLE IF EXISTS `orderdetail`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `orderdetail` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`orders_id` int(11) NOT NULL,
`items_id` int(11) NOT NULL,
`items_num` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_orderdetail_1` (`orders_id`),
KEY `FK_orderdetail_2` (`items_id`),
CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `orders`
--
DROP TABLE IF EXISTS `orders`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`number` varchar(32) NOT NULL,
`createtime` datetime NOT NULL,
`note` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_orders_1` (`user_id`),
CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `user`
--
DROP TABLE IF EXISTS `user`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`birthday` date DEFAULT NULL,
`sex` char(1) DEFAULT NULL,
`address` varchar(256) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2020-07-25 14:03:26
- 导入
data.sql
创建初始数据
-- MySQL dump 10.13 Distrib 5.7.17, for Win64 (x86_64)
--
-- Host: localhost Database: mybatis
-- ------------------------------------------------------
-- Server version 5.7.19-log
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Dumping data for table `items`
--
LOCK TABLES `items` WRITE;
/*!40000 ALTER TABLE `items` DISABLE KEYS */;
INSERT INTO `items` VALUES (1,'Computer',3000.0,'This computer is nice',NULL,'2020-07-23 13:22:53'),(2,'Laptop',6000.0,'This Laptop is nice',NULL,'2020-07-23 13:22:57'),(3,'Bag',200.0,'This bag is nice',NULL,'2020-07-23 13:23:02');
/*!40000 ALTER TABLE `items` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Dumping data for table `orderdetail`
--
LOCK TABLES `orderdetail` WRITE;
/*!40000 ALTER TABLE `orderdetail` DISABLE KEYS */;
INSERT INTO `orderdetail` VALUES (1,3,1,1),(2,3,2,3),(3,4,3,4),(4,4,2,3);
/*!40000 ALTER TABLE `orderdetail` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Dumping data for table `orders`
--
LOCK TABLES `orders` WRITE;
/*!40000 ALTER TABLE `orders` DISABLE KEYS */;
INSERT INTO `orders` VALUES (3,1,'1000010','2020-07-23 13:22:35',NULL),(4,1,'1000011','2020-07-23 13:22:41',NULL),(5,10,'1000012','2020-07-23 16:13:23',NULL);
/*!40000 ALTER TABLE `orders` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Dumping data for table `user`
--
LOCK TABLES `user` WRITE;
/*!40000 ALTER TABLE `user` DISABLE KEYS */;
INSERT INTO `user` VALUES (1,'Wang Wu',NULL,'2',NULL),(10,'Zhang San','1990-07-10','1','BeiJing'),(16,'Li Si',NULL,'1','ShangHai'),(22,'Chen Xiaoming',NULL,'1','XinJiang'),(24,'Zhang Sanfeng',NULL,'1','HaErBin'),(25,'Chen Xiaomi',NULL,'1','GuangXi'),(26,'Wang Wu',NULL,NULL,NULL);
/*!40000 ALTER TABLE `user` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2020-07-25 14:04:44
经历上述步骤,如果没有出现问题的话,用于测试的初始环境就算是创建完成了。
2. 入门程序
2.1 根据ID查询用户
1. 数据库配置文件db.properties。本例中,创建了一个名为config
的源文件夹(Source Folder),文件放在该目录下,后续配置文件除了特别说明外都放在该目录下。
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.name=root
jdbc.password=root
2. log4j.properties
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#这里的输出格式可以根据需要自定义
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
3. 创建POJO类(简单JAVA对象,用来映射结果集)。POJO类中的字段要与数据表中的列名相对应,并需要有getter/setter方法。下图为用户表的列名。
import java.util.Date;
public class User {
private int id;
private String sex;
private String username;
private Date birthday;
private String address;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [id=" + id + ", sex=" + sex + ", username=" + username
+ ", birthday=" + birthday + ", address=" + address + "]";
}
}
4. 映射文件User.xml。本例中该文件放于config/sqlmap
文件夹中,前面提到config
文件夹是一个源文件夹。
<?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">
<!-- 通过id查询用户 -->
<mapper namespace="test">
<select id="findUserById" parameterType="int" resultType="com.shao.pojo.User">
SELECT * FROM USER WHERE id=#{id}
</select>
</mapper>
映射文件为xml
格式,整个xml
实质内容都在<mapper></mapper>
中,namespace
指定命名空间,与下面的id
一起,用于指定具体的映射块。因为本例中属于查询,所以映射块用<select></select>
括起来。parameterType
指定入参类型,resultType
指定出参类型,本例中直接映射成POJO
类。
在<select>
和</select>
之间放的是sql
语句,id=#{id}
中#{}
表示占位符,在使用时,实参将被传递到这里代替占位符。这样,一个简单的映射文件就形成了。
sql语句,出入参类型都写到配置文件里面去了,解决了JDBC 中硬编码的问题。
5. MyBatis的全局配置文件SqlMapConfig.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="db.properties" />
<!-- 默认是development环境 -->
<environments default="development">
<environment id="development">
<!-- 事务管理使用JDBC,事务控制由mybatis来负责 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池,由mybatis管理 -->
<dataSource type="POOLED">
<!-- 从数据库配置文件中读取数据库参数 -->
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.name}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- 在这里配置映射文件,本例中为放在config目录下的sqlmap/User.xml,因为config是源文件夹,所以不用写 -->
<mappers>
<mapper resource="sqlmap/User.xml" />
</mappers>
</configuration>
SqlMapConfig.xml
是MyBatis
的全局配置文件,可以说还是比较重要,后面会有单独的章节对里面的参数配置进行说明。因为是入门程序,所以这里先不展开。
配置文件中配置了数据库连接池(这里面没有配置长连接数量、最大连接数量等参数),一定程度上解决了JDBC
用就打开不用就关闭带来的数据库性能问题。
6. 测试。本例用了JUnit 4
。
@Test
public void testFindUserById() {
try {
// 读取全局配置文件
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//框架中的会话工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
//由会话工厂创建会话,通过会话具体操作数据库
SqlSession sqlSession = sessionFactory.openSession();
//因为只查询一条,所以用selectOne,传了两个实参,一个实参是“命名空间.id”用于匹配映射文件中的具体内容,另一个是传递到sql语句中代替占位符的实际量。
User user = sqlSession.selectOne("test.findUserById", 10);
System.out.println(user);
sqlSession.close();
} catch (IOException e) {
e.printStackTrace();
}
}
输出如下(截取部分)。因为我们在映射文件中要求出参类型是POJO型,所以我们可以看到User [id=10, sex=1, username=Zhang San, birthday=Tue Jul 10 00:00:00 CDT 1990, address=BeiJing]
已经映射成了POJO
型。这从一定程度上解决了JDBC ResultSet
结果集获取数据比较麻烦的事情。
2.2 根据名称模糊查询用户
这里的名称,指的是MySQL
表里面的username
。根据名称模糊查询,用到的是sql
语句中的‘like’
关键字。
需要注意的是,模糊查询,可能会查询到多条记录,所以结果不再是单条记录,我们需要用一个List
来保存结果。
基本的环境和代码条件前一步都已经具备了,这里只需要在映射文件中加一点内容,然后再写一个测试程序就行了。
1. 映射文件User.xml
中添加以下内容(在<mapper></mapper>
中添加,与前面一段映射块平级)
<!-- 通过用户名模糊查询 -->
<select id="findUserByName" parameterType="java.lang.String"
resultType="com.shao.pojo.User">
SELECT * FROM USER WHERE username LIKE '%${value}%'
</select>
需要注意两点:
(1)这里,resultType
还是POJO
类而不是List
;
(2)${}
表示拼接字符串,将接收到的参数不加修饰的拼接到sql
语句中,使用该方式拼接sql
语句,会引起sql
注入;另外,当传入的是简单类型是,sql
语句中${}
里放的只能是value
,试着将单词value
修改为其它时,程序将无法运行。
2. 测试程序
/**
* 通过用户名模糊查询
*/
@Test
public void findUserByNameTest(){
try {
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession = sessionFactory.openSession();
List<User> list=sqlSession.selectList("test.findUserByName", "Zhang");
System.out.println(list);
sqlSession.close();
} catch (Exception e) {
e.printStackTrace();
}
}
这里也需要注意两点:
(1)可以看到会有代码重复,但是这里只是入门程序,后面会专门说到会话工厂和会话的使用范围,这里不过多强调;
(2)因为查询结果可能存在多条,所以用的是List
来接受结果,用resultType
中定义的POJO
类(也就是User
)来限定List
中存储的类型,然后用selectList
来代替了前一章节中的selectOne
。这里再次强调,不管是selectOne
还是selectList
还是接下来的增、删、改,都需要注意,传入的参数一定和映射文件中namespace.id
能对应上。
来看一下输出(截取部分):
从输出结果看,测试程序中传入的“Zhang”
确实是拼接到sql语句中去了,查出来的结果也不止一条,证明程序能正常运行。
2.3 添加用户
该说的前面都说的差不多了,这里就不赘述了,直接看映射文件中的配置和测试程序吧。
1. 映射文件(User.xml
)中添加
<insert id="insertUser" parameterType="com.shao.pojo.User">
INSERT INTO
USER(username,sex,birthday,address)
VALUE(#{username},#{sex},#{birthday},#{address})
<!-- 主键返回 -->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
<!-- 适用于自增主键 -->
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
这里要注意,因为数据表中,用户id被设置为自增,所以插入时就不需要指定id
了。另外,由于是id
为主键且自增,当有需要时,可以通过嵌套<selectKey>
标签获取自增主键,相关的sql
语句为SELECT LAST_INSERT_ID()
。当不嵌套<selectKey>
标签时自增主键返回默认值。
keyProperty
对应的是自增主键在MYSQL
表中的字段名;order
指定SELECT LAST_INSERT_ID()
相对insert
语句的执行顺序。
2. 测试程序
/**
* 插入新用户
*/
@Test
public void insertUserTest(){
try {
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession = sessionFactory.openSession();
User user=new User();
//或者也可以使用POJO中的setter赋值,当属性比较多比较复杂的时候,也可以考虑使用建造者模式
user.setUsername("Li Xiaopeng");
user.setSex("2");
user.setBirthday(Date.from(LocalDate.of(1999, 1, 9).atStartOfDay(ZoneId.systemDefault()).toInstant()));
user.setAddress("Shanghai");
sqlSession.insert("test.insertUser", user);
sqlSession.commit();
//在自增主键的情况下User.xml必须指定selectType类型,否则查询不到
System.out.println("--------------------" + user.getId() + "--------------------");
sqlSession.close();
} catch (Exception e) {
e.printStackTrace();
}
}
需要注意一下:
增、删、改操作都需要调用sqlSession.commit();
进行手动提交。
来看一下输出(截取部分):
从输出结果看,已经插入成功,而且自增的主键也获取到了。、
下面是MySQL
中数据表中的数据,可以看到最后一条记录就是刚刚插入的。
3. 补充一个小知识
当主键为非自增并需要返回时,可以使用MySQL
的uuid()
函数,使用这个函数的前提是:将主键的字段类型设置为字符串类型,并且长度设置为35位。uuid
产生的是字符串类型值,固定长度为36
个字符。先通过uuid()
函数查询到主键,再返回到sql
语句中。
需要注意的是,使用uuid()
时,相对于insert
语句的指向顺序是“BEFORE”
,同时,因为主键不是自增,所以理所应当的,insert
语句中要插入主键。
2.4 删除用户
1. 映射文件(User.xml
)
删除就简单了,因为删除没有什么可返回的,所以只需要定义id
和入参类型即可。
<!-- 删除用户 -->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{id}
</delete>
2. 测试程序
@Test
public void deleteUserTest(){
try {
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession = sessionFactory.openSession();
sqlSession.delete("test.deleteUser", 30);
sqlSession.commit();
sqlSession.close();
} catch (Exception e) {
e.printStackTrace();
}
}
把刚才刚插入的一条新纪录给删了,下面的数据表说明,已经删除成功了。
再来看看输出好了(截取部分):
2.5 更新用户信息
1. 映射文件(User.xml)
<update id="updateUser" parameterType="com.shao.pojo.User">
update user set username=#{username} where id=#{id}
</update>
需要改什么就在sql
语句中添加相关的字段即可,本例中只是修改用户名。
2. 测试程序
@Test
public void updateUserTest(){
try {
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession = sessionFactory.openSession();
User user=new User();
user.setId(22);
user.setUsername("Test Update");
sqlSession.update("test.updateUser", user);
sqlSession.commit();
sqlSession.close();
} catch (Exception e) {
e.printStackTrace();
}
}
需要注意:因为是根据id
来对数据进行修改,所以id
字段是必须要存在的。更新用户时,可以新建一个用户对象,设置好id
后,设置需要修改的字段并插入(没有设置的字段会保持不变);也可以在已有对象的基础上进行克隆操作(注意浅克隆和深克隆的区分)之后插入。
可以看到数据表中相关的信息已经被修改了。
再来看看截取的部分输出内容:
2.6 工程结构
还是把工程结构放这儿。(#)
表示Source Folder
。
有几个包是后面才用到的,为了不引起误导,先屏蔽掉。
3. 总结
本文主要介绍了MyBatis
的几个简单的入门程序。用的环境是MySQL5.7
和 MyBatis 3.4.4
。从程序中也可以看出来,MyBatis
确实解决了上一文中提到的若干问题,编程的主要关键点还是在配置文件中,可见配置文件在MyBatis
编程中中还是很重要的。
需要注意的一些细节问题在文中各处也都说了,限于篇幅,这里不赘述。
4. 参考文献
【1】 MyBatis User Guide
【2】传智 “SpringMVC+MyBatis由浅入深”教程