基础入门案例
项目整体结构
创建一个maven项目
不用导入骨架,pom.xml配置如下:
(这里我的数据库是8.0.18,mybatis是3.4.5,根据不同的版本可调整自己的配置)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sjh</groupId>
<artifactId>mybatis</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
数据库的准备
这里我创建在test数据库中,根据自己的需要调整
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(32) NOT NULL COMMENT '用户名称',
`birthday` DATETIME DEFAULT NULL COMMENT '生日',
`sex` CHAR(1) DEFAULT NULL COMMENT '性别',
`address` VARCHAR(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user`(`id`,`username`,`birthday`,`sex`,`address`)
VALUES (41,'老王','2018-02-27 17:47:08','男','北京'),
(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),
(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),
(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),
(46,'老王','2018-03-07 17:37:26','男','北京'),
(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');
创建实体类User
用来封装查询数据库返回的结果
创建一个User类,并创建对应的setter/getter和toString方法,这里建议使用包装类型,因为当数据不存在时返回null值,但基本类型是不能接收的,会报错。
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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 getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
创建dao接口
public interface IUserDao {
//查询所有用户
List<User> findAll();
}
创建主配置文件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>
<!-- 环境配置 -->
<environments default="mysql">
<!-- id和上面的值相同 -->
<environment id="mysql">
<!-- 事务类型为jdbc -->
<transactionManager type="JDBC"/>
<!-- 数据源配置 -->
<dataSource type="POOLED">
<!-- 驱动 url 用户名 密码 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///test?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="sjh2019"/>
</dataSource>
</environment>
</environments>
<!-- 配置映射 -->
<mappers>
<!-- resource 值是映射的xml文件位置 -->
<mapper resource="dao/IUserDao.xml" />
</mappers>
</configuration>
创建映射配置文件IUserDao.xml
放在resources目录下,结构要和对应的接口目录结构一致
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
//namespace值为对应接口全限定类名
<mapper namespace="dao.IUserDao">
<!-- id是要调用的方法名 resultType是要返回的类型-->
<select id="findAll" resultType="domain.User">
<!-- 此处写具体的sql语句 -->
select * from user;
</select>
</mapper>
测试类
在test包中创建测试类
public class MybatisTest {
@Test
//查询所有用户
public void test() throws IOException {
//读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建sqlsession工厂对象
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(inputStream);
//创建sqlsession对象
SqlSession sqlSession = factory.openSession();
//创建IUserDao代理类
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
//调用方法
List<User> users = userDao.findAll();
//打印结果
users.forEach(System.out::println);
//关闭资源
inputStream.close();
}
}
运行结果
使用注解配置的方式实现案例
以上是使用xml的方式,接下来介绍使用注解的方式
首先删除掉映射配置文件(否则会报错,因为会优先使用xml配置),在主配置文件中,修改mappers属性
使用注解配置时,设置class的值为dao接口全限定类名
<!-- 配置映射 -->
<mappers>
<!-- resource 值是映射的xml文件位置 -->
<!--<mapper resource="dao/IUserDao.xml" />-->
<mapper class="dao.IUserDao"/>
</mappers>
接着在dao接口的方法上加上注解
public interface IUserDao {
//查询所有用户
@Select("select * from user")
List<User> findAll();
}
之后运行,可得到相同结果。
抽取重复代码
public class MybatisTest {
private InputStream inputStream;
private SqlSession sqlSession;
private IUserDao userDao;
@Before//在Test执行前执行
public void init() throws IOException {
//读取配置文件
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建sqlsession工厂对象
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(inputStream);
//创建sqlsession对象
sqlSession = factory.openSession();
//创建IUserDao代理类
userDao = sqlSession.getMapper(IUserDao.class);
}
@After
public void destroy() throws IOException {
//提交事务
sqlSession.commit();
//关闭资源
inputStream.close();
}
@Test
//查询所有用户
public void test() throws IOException {
//调用方法
List<User> users = userDao.findAll();
//打印结果
users.forEach(System.out::println);
}
}
Mybatis 的CRUD操作
保存数据
1 在dao接口添加方法
//保存用户
void saveUser(User user);
2 配置映射
在映射配置wenj文件IUserDao.xml的mapper中加入
插入格式使用#{封装类的属性名}
,parameterType值是参数类的全限定类名
<insert id="saveUser" parameterType="domain.User">
insert into user(username,birthday,sex,address)
values (#{username},#{birthday},#{sex},#{address});
</insert>
3 测试方法
@Test
//保存用户
public void save(){
//创建要保存的用户对象
User user = new User();
user.setUsername("范闲");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("南庆");
//调用方法
userDao.saveUser(user);
}
4 结果
更新数据
1 在dao接口添加方法
//更新用户
void updateUser(User user);
2 配置映射
<update id="updateUser" parameterType="domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id};
</update>
3 测试方法
@Test
//更新用户
public void update(){
//创建要更新的用户对象
User user = new User();
user.setId(49);
user.setUsername("司理理");
user.setBirthday(new Date());
user.setSex("女");
user.setAddress("北齐");
//调用方法
userDao.updateUser(user);
}
4 结果
删除数据
1 在dao接口添加方法
//删除用户
void deleteUser(Integer id);
2 配置映射
当只有一个属性时,名称可任意,这里我任意起名randomid(实际即dao接口方法的参数id)
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{randomid};
</delete>
3 测试方法
@Test
//删除用户
public void delete(){
//调用方法
userDao.deleteUser(49);
}
4 结果
id49的记录被删掉了
查询一个数据和模糊查询
1 在dao接口添加方法
//查询一个用户
User findById(Integer id);
//根据名字模糊查询
User findLike(String name);
2 配置映射
<select id="findById" parameterType="INT" resultType="domain.User">
select * from user where id=#{uid};
</select>
<select id="findLike" parameterType="string" resultType="domain.User">
select * from user where username like #{likename};
</select>
模糊查询时也可以使用select * from user where username like '%${value}%;
,但该方法相当于拼接字符串,不安全,因此不推荐使用。
------------------------`
3 测试方法
@Test
//查询一个用户
public void findbyid(){
//调用方法
User user = userDao.findById(48);
System.out.println(user);
}
@Test
//模糊查询用户
public void findlike(){
//调用方法
List<User> users = userDao.findLike("%王%");
users.forEach(System.out::println);
}
4 结果
根据id48查询:
根据“王”模糊查询:
查询记录总数
1 在dao接口添加方法
//查询记录总数
int count();
2 配置映射
<select id="count" resultType="int">
select count(*) from user ;
</select>
3 测试方法
@Test
//查询记录数
public void count(){
//调用方法
int count = userDao.count();
System.out.println(count);
}
4 结果
在保存数据时 获取id属性
1 配置映射
在之前的insert标签中加入selectKey标签。
keyProperty
:对应的实体类属性名
keyColumn
:对应的数据库列名
order
:执行顺序,after代表在insert语句执行后执行
<insert id="saveUser" parameterType="domain.User">
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address});
</insert>
2 测试方法
@Test
//保存用户
public void save(){
//创建要保存的用户对象
User user = new User();
user.setUsername("范闲");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("南庆");
System.out.println("运行前"+user);
//调用方法
userDao.saveUser(user);
System.out.println("运行后"+user);
}
3 结果
Mybatis的参数深入
ParameterType
-
传递简单类型
-
传递pojo对象
Mybatis使用ognl表达式解析对象字段的值,#{}或者${}中的值为pojo属性名称 -
传递pojo包装对象
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。Pojo 类中包含 pojo。
案例:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。- 创建Pojo包装类
创建QueryVo,其中属性设置为User类
public class QueryVo { private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
- 在dao接口添加方法
//根据Pojo包装类实现模糊查询 List<User> findByVo(QueryVo queryVo);
- 修改映射配置
<select id="findByVo" parameterType="domain.QueryVo" resultType="domain.User"> select * from user where username like #{user.username}; </select>
- 测试方法
@Test //模糊查询用户 public void findVo(){ QueryVo queryVo=new QueryVo(); User user=new User(); user.setUsername("%王%"); queryVo.setUser(user); //调用方法 List<User> users = userDao.findByVo(queryVo); users.forEach(System.out::println); }
- 结果
- 创建Pojo包装类
实体属性名和数据库列名不同的解决
resultMap 结果类型(开发效率高)
resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。 在 select 标签中使用
resultMap
属性指定引用即可。
同时 resultMap 可以实现将查询结果映射为复杂类 型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。
定义 resultMap
<!-- 建立 User 实体和数据库表的对应关系
type 属性:指定实体类的全限定类名
id 属性:给定一个唯一标识,是给查询 select 标签引用用的。
-->
<resultMap type="domain.User" id="userMap">
<id column="id" property="userId"/>
<result column="username" property="userName"/>
<result column="sex" property="userSex"/>
<result column="address" property="userAddress"/>
<result column="birthday" property="userBirthday"/>
</resultMap>
id 标签:用于指定主键字段
result 标签:用于指定非主键字段
column 属性:用于指定数据库列名 property
属性:用于指定实体类属性名称
映射配置
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
select * from user
</select>
测试结果
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for(User user : users) {
System.out.println(user);
} }
运行结果
使用别名(执行效率高)
例:
<select id="findAll" resultType="domain.User">
select id as userId,username as userName,birthday as userBirthday from user;
</select>
配置文件标签使用的补充
properties标签
之前的SqlMapConfig.xml
中property
标签配置的数据库信息可配置在properties
标签中,并使用${}
引用
也可配置外部文件,在resources
目录下创建一个jdbc.properties
resource 属性:用于指定 properties 配置文件的位置,要求配置文件必须在类路径下
typeAliases标签
typeAlias
使用该标签,可给实体类起别名,不需要在配置时再写全限定类名
<typeAliases>
<typeAlias type="domain.User" alias="user"/>
</typeAliases>
type是实体类全限定类名,alias是别名,使用后不区分大小写
可直接使用别名
package
也是用来起别名的,该值指定一个包,该包中的所有类自动设置别名为类名/接口名,且不区分大小写
在IUserDao.xml配置中,不用再写全限定类名
Mybatis 的连接池技术与事务控制
分类
可以看出 Mybatis 将它自己的数据源分为三类:
- UNPOOLED 不使用连接池的数据源
- POOLED 使用连接池的数据源
- JNDI 使用 JNDI 实现的数据源
具体结构如下:
相应地,MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSource,
PooledDataSource 类来表示 UNPOOLED、POOLED 类型的数据源
在这三种数据源中,我们一般采用的是 POOLED 数据源(很多时候我们所说的数据源就是为了更好的管理数据库连接,也就是我们所说的连接池技术)。
我们的数据源配置就是在 SqlMapConfig.xml 文件中,具体配置如下:
<!-- 配置数据源(连接池)信息 -->
<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 在初始化时,根据<dataSource>
的type
属性来创建相应类型的的数据源 DataSource,即:
type="POOLED"
:MyBatis 会创建 PooledDataSource 实例
type="UNPOOLED"
: MyBatis 会创建 UnpooledDataSource 实例
type="JNDI"
:MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用
事务控制
- 在 JDBC 中我们可以通过手动方式将事务的提交改为手动方式,通过
setAutoCommit()
方法就可以调整。 - Mybatis 框架因为是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的
setAutoCommit()
方法来设置事务提交方式的。 - 当使用sqlSession工厂的openSession方法时,如果空参或false代表关闭事务的自动提交,如果传入true代表打开事务自动提交。