基于代理Dao实现CRUD操作
根据ID查询
- 持久层接口中添加findById方法
1 | /** |
- 在用户的映射配置文件中配置
1 | <!-- 根据id查询用户 --> |
细节:
- resultType属性: 用于指定结果集的类型。
- parameterType属性:用于指定传入参数的类型。
- sql语句中使用#{}字符: 它代表占位符,相当于原来jdbc部分的 ?,都是用于执行语句时替换实际的数据。 具体的数据是由#{}里面的内容决定的。
- #{}中内容的写法: 由于数据类型是基本类型,所以此处可以随意写。
在测试类添加测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public class MybatisTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
//用于在测试方法执行之前执行
public void init() throws Exception {
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession对象
sqlSession = factory.openSession();
//4.获取dao的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
//用于在测试方法执行之后执行
public void destroy() throws Exception {
//要手动提交事务(autocommit默认为false)
sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
public void testFindOne(){
User user = userDao.findById(41);
System.out.println(user);
}
}保存操作
在持久层接口中添加保存方法
1
2
3
4
5/**
* 保存用户
* @param user
*/
void saveUser(User user);
在用户的映射配置文件中配置
1
2
3
4<!-- 保存用户 -->
<insert id="saveUser" parameterType="wzc.domain.User">
INSERT INTO user(username,address,sex,birthday) VALUES(#{username},#{address},#{sex},#{birthday});
</insert>细节:
- parameterType属性: 代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。
- sql语句中使用#{}字符: 它代表占位符,相当于原来jdbc部分所学的?,都是用于执行语句时替换实际的数据。 具体的数据是由#{}里面的内容决定的。
- #{}中内容的写法: 由于我们保存方法的参数是 一个User对象,此处要写User对象中的属性名称。 它用的是ognl表达式。
- ognl表达式: 它是apache提供的一种表达式语言,全称是: Object Graphic Navigation Language 对象图导航语言 它是按照一定的语法格式来获取数据的。 语法格式就是使用 #{对象.属性}的方式. #{user.username}会先去找user对象,然后在user对象中找到username属性,并调用getUsername()方法把值取出来。但是我们在parameterType属性上指定了实体类名称,所以可以省略user.而直接写username。
添加测试类中的测试方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/**
* 测试保存操作
*/
public void testSave() {
User user = new User();
user.setUsername("mybatis last insertid");
user.setAddress("南京市玄武区");
user.setSex("男");
user.setBirthday(new Date());
System.out.println("保存操作之前:" + user);
//执行保存方法
userDao.saveUser(user);
System.out.println("保存操作之后:" + user);
}注意: destroy()方法中的sqlSession.commit();实现了事务的提交.
问题扩展:新增用户id的返回值
新增用户后,同时还要返回当前新增用户的id值,因为id是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长auto_increment的值返回。
1
2
3
4
5
6
7
8<!-- 保存用户 -->
<insert id="saveUser" parameterType="wzc.domain.User">
<!-- 配置插入操作后,获取插入数据的id -->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
SELECT last_insert_id();
</selectKey>
INSERT INTO user(username,address,sex,birthday) VALUES(#{username},#{address},#{sex},#{birthday});
</insert>更新操作
在持久层接口中添加更新方法
1
2
3
4
5/**
* 更新用户
* @param user
*/
void updateUser(User user);
在用户映射配置文件中配置
1
2
3
4<!-- 更新用户 -->
<update id="updateUser" parameterType="wzc.domain.User">
UPDATE user SET username = #{username}, address = #{address}, sex = #{sex}, birthday = #{birthday} WHERE id = #{id};
</update>
加入更新测试方法
1
2
3
4
5
6
7
8
9
10
11
12
public void testUpdate() {
User user = new User();
user.setId(50);
user.setUsername("mybatis Update user");
user.setAddress("南京市玄武区");
user.setSex("女");
user.setBirthday(new Date());
//5.执行更新方法
userDao.updateUser(user);
}删除操作
在持久层就扣中添加删除方法
1
2
3
4
5/**
* 根据Id删除用户
* @param userId
*/
void deleteUser(Integer userId);在用户映射配置文件中配置
1
2
3
4<!-- 删除用户 -->
<delete id="deleteUser" parameterType="int">
DELETE FROM user WHERE id = #{uid};
</delete>注: 只有一个占位符, 且parameterType为基本类型时, 大括号{}内可以写任何字符
加入删除测试方法
1
2
3
4
5
public void testDelete() {
//执行删除方法
userDao.deleteUser(51);
}
模糊查询
在持久层接口中添加模糊查询方法
1
2
3
4
5
6/**
* 根据名称模糊查询用户信息
* @param username
* @return
*/
List<User> findByName(String username);在用户的映射配置文件中配置
1
2
3
4
5<!-- 根据名称模糊查询 -->
<select id="findByName" parameterType="String" resultType="wzc.domain.User">
SELECT * FROM user WHERE username LIKE #{name};
<!-- SELECT * FROM user WHERE username LIKE '%${value}%'; -->
</select>加入模糊查询的测试方法
1
2
3
4
5
6
7
8
9
10
11
public void testFindByName() {
//执行查询一个方法
List<User> users = userDao.findByName("%王%");
// List<User> users = userDao.findByName("王");
for (User user : users) {
System.out.println(user);
}
}
模糊查询的另一种配置方式
修改SQL语句配置, 且测试类中不再需要写%
1
SELECT * FROM user WHERE username LIKE '%${value}%';
- #{}与${}的区别
#{}表示一个占位符号
通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
${}表示拼接sql串
通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。
使用聚合函数查询
在持久层接口中添加查询方法
1
2
3
4
5/**
* 查询总用户数
* @return
*/
int findTotal();在用户的映射配置文件中配置
1
2
3
4<!-- 获取用户的总记录条数 -->
<select id="findTotal" resultType="int">
SELECT COUNT(id) FROM user;
</select>加入聚合查询的测试方法
1
2
3
4
5
6
public void testFindTotal() {
//执行查询总记录方法
int count = userDao.findTotal();
System.out.println(count);
}MyBatis与JDBC编程比较总结
JDBC: 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
MyBatis: 在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。
JDBC:Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
MyBatis:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
JDBC:向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数对应。
MyBatis:自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
JDBC:对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
MyBatis:自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
MyBatis的参数深入
parameterType配置参数
SQL语句传参,使用标签的parameterType属性来设定。该属性的取值可以是基本类型,引用类型(例如:String类型),还可以是实体类类型(POJO类)。同时也可以使用实体类的包装类. 下面介绍如何使用实体类的包装类作为参数传递.
基本类型和String我们可以直接写类型名称,也可以使用包名.类名的方式,例如:java.lang.String。
实体类类型,目前只能使用全限定类名。mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。(在注册实体类别名之前)
传递pojo包装对象
编写QueryVo
1
2
3
4
5
6
7
8
9
10
11
12public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}持久层接口的映射文件
1
2
3
4<!-- 根据queryVo的条件查询用户 -->
<select id="findUserByVo" parameterType="wzc.domain.QueryVo" resultType="wzc.domain.User">
SELECT * FROM user WHERE username LIKE #{user.username};
</select>测试包装类作为参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void testFindByVo() {
QueryVo vo = new QueryVo();
User user = new User();
user.setUsername("%王%");
vo.setUser(user);
//执行查询方法,参数是一个QueryVo对象
List<User> users = userDao.findUserByVo(vo);
for (User u : users) {
System.out.println(u);
}
}
MyBatis的输出结果封装
resultType配置结果类型
resultType属性可以指定结果集的类型,它支持基本类型和实体类类型。需要注意的是,它和parameterType一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。
基本类型示例(如聚合函数查询, 返回int类型)
实体类类型示例(如查询所有操作, 返回List
类型) 特殊情况示例(实体类属性名称和数据库表的列名不一致)
- 修改实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class User implements Serializable {
private Integer userId; //数据库列名id
private String userName; //数据库列名username
private Date userBirthday; //数据库列名birthday
private String userSex; //数据库列名sex
private String userAddress; //数据库列名address
public Integer getUserId() { return userId; }
public void setUserId(Integer userId) { this.userId = userId; }
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public Date getUserBirthday() { return userBirthday; }
public void setUserBirthday(Date userBirthday) {this.userBirthday = userBirthday; } public String getUserSex() { return userSex; }
public void setUserSex(String userSex) { this.userSex = userSex; }
public String getUserAddress() { return userAddress; }
public void setUserAddress(String userAddress) { this.userAddress = userAddress; }
public String toString() { return "User [userId=" + userId + ", userName=" + userName + ", userBirthday=" + userBirthday + ", userSex=" + userSex + ", userAddress=" + userAddress + "]"; } }Dao接口中添加查询所有方法
1
2
3
4
5/**
* 查询所有用户
* @return
*/
List<User> findAll();映射配置
1
2
3
4<!-- 查询所有 -->
<select id="findAll" resultType="wzc.domain.User">
SELECT * FROM user;
</select>测试查询结果
1
2
3
4
5
6
7
8
public void testFindAll() {
//5.执行查询所有方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}测试结果: 除了userName有值, 其他都是null
原因: mysql在windows系统中不区分大小写.所以实体类属性userName与数据库列名username是一致的.
resultMap结果类型
resultMap标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。 在select标签中使用resultMap属性指定引用即可。同时resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。
使用resultMap解决上一节实体类无法封装的问题
定义resultMap
1
2
3
4
5
6
7
8
9
10
11<!-- 建立User实体和数据库表的对应关系
type属性:指定实体类的全限定类名
id属性:给定一个唯一标识,是给查询select标签引用用的。
-->
<resultMap type="wzc.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属性:用于指定实体类属性名称
映射配置
1
2
3
4<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
select * from user
</select>测试查询结果
1
2
3
4
5
6
7
8
public void testFindAll() {
//执行查询所有方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}测试结果: 所有属性值封装成功.
SqlMapConfig.xml配置文件
配置内容
SqlMapConfig.xml配置的内容和顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33-properties(属性)
--property
-settings(全局配置参数)
--setting
-typeAliases(类型别名)
--typeAliase
--package
-typeHandlers(类型处理器)
-objectFactory(对象工厂)
-plugins(插件)
-environments(环境集合属性对象)
--environment(环境子属性对象)
---transactionManager(事务管理)
---dataSource(数据源)
-mappers(映射器)
--mapper
--package
properties(属性)
在classpath下定义jdbcConfig.properties
1
2
3
4jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=xxxxdataSource标签配置
1
2
3
4
5
6<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
typeAliases(别名)
自定义别名
1
2
3
4
5
6
7
8
9<!-- 使用typeAliases配置别名, 它只能配置domain中类的别名-->
<typeAliases>
<!-- typeAlias用于配置别名. type属性指定的是实体类全限定类名. alias属性指定别名,当指定了别名就不再区分大小写 -->
<!-- 单个别名定义 -->
<typeAlias type="wzc.domain.User" alias="user"></typeAlias>
<!-- 批量别名定义, 指定后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写 -->
<package name="wzc.domain"></package>
</typeAliases>
mappers(映射器)
使用相对于类路径的资源, 如:
1
<mapper resource="wzc/dao/IUserDao.xml" />
注意: resource属性的资源路径是用/分割
使用mapper接口类路径, 如:
1
<mapper class="wzc.dao.UserDao"/>
注意:class属性的类名用.分割. 而且此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
注册指定包下的所有mapper接口, 如:
1
<package name="wzc.dao"/>
注意:package标签是用于指定dao接口所在的包,当在typeAliases中指定package name之后就不需要再写mapper以及resource或者class了.