本人小白一枚,欢迎大家一起讨论学习,如有错误,还望大家指教。
基于代理Dao的CRUD操作
接着上一篇,我们继续来深入Mybatis框架,这里因为我需要打印日志信息和单元测试,所以我在pom文件中新添加了两个坐标。
<!-- 日志坐标 -->
<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>
- 根据ID查询,在UserDao接口中添加findById方法。
public interface UserDao {
List<User> findAll();
User findById(int id);
}
- 在UserDao.xml配置文件中添加findById的操作,因为这里需要一个整型参数,所以我们需要声明参数类型,即在parameterType中声明,并在sql语句中声明参数的占位符,即
#{}
,这个相当于jdbc中的?
占位符。注意,#{id}
这里的内容可以随便写,因为参数是个基本类型,并且只有一个参数。
<select id="findById" resultType="entity.User" parameterType="int">
SELECT * FROM user WHERE id = #{id}
</select>
- 测试findById方法,我在test下创建了一个Demo类,并在这个类下对各个方法进行测试。
public class Demo {
private SqlSession session;
private InputStream inputStream;
private UserDao dao;
@Before
public void init() throws Exception{
inputStream = Resources.getResourceAsStream("MapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
session = factory.openSession();
dao = session.getMapper(UserDao.class);
}
@Test
public void findByIdTest() {
User user = dao.findById(48);
System.out.println(user);
}
@After
public void destroy() throws Exception{
inputStream.close();
session.close();
}
}
- 添加保存用户操作,在UserDao接口里添加一个saveUser方法。
/**
* 保存用户
* @param user
* @return 影响数据库记录的行数
* */
int saveUser(User user);
- 在UserDao.xml映射文件中声明savaUser方法的操作,细心的小伙伴可以发现,我们saveUser方法是有一个整型的返回值,但我们在映射文件中却没有声明resultType属性,我们要是写上这个resultType这个属性,idea工具也会为咱们标红,提示错误。所以我们就可以知道它默认返回一个int,也就是影响的行数。
#{}
中内容的写法:由于我们保存方法的参数是一个User对象,所以此处要写User对象中的属性名称。这里用的的是ognl
表达式。它是由apache提供的一种表达式语言,全称是Object Graphic Navigation Language译对象导航图语言
,语法格式为#{对象.属性}
。例如#{user.username}它会先去找user对象,之后再user对象中找username属性,并调用getUsername()方法把值取出来。因为我们在parameterType属性上指定了实体类名称,所有可以省略的user.直接写的username
<insert id="saveUser" parameterType="entity.User"">
<selectKey keyProperty="id" keyColumn="id" resultType="int">
SELECT last_insert_id();
</selectKey>
INSERT INTO USER(username, birthday, sex, address) VALUES(#{username}, #{birthday}, #{sex}, #{address})
</insert>
- 测试saveUser方法,我们发现影响的行数是1,但是我们刷新数据库会发现没有我们保存的这条记录,通过查看日志信息我们会发现,事物进行了回滚。这一点和JDBC一样,我们在实现增删改时一定要控制事务的提交,在Mybatis我可以通过SqlSession对象完成对事务的提交,即
SqlSession对象.commit();
@Test
public void saveUserTest() {
User user = new User();
user.setUsername("刘灰灰");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("火星");
System.out.println("影响的行数:" + dao.saveUser(user));
}
- 扩展:当我们插入完数据后,如果我们想要获取新增用户的id,我们可以通过如下进行获取(这里的id是自增长的)。
<insert id="saveUser" parameterType="entity.User">
<selectKey keyProperty="id" keyColumn="id" resultType="int">
SELECT last_insert_id();
</selectKey>
INSERT INTO USER(username, birthday, sex, address) VALUES(#{username}, #{birthday}, #{sex}, #{address})
</insert>
- 再次测试我们saveUser方法,这次我们不要忘记提交事务,这次我们可以看到,保存后的User对象的id为59,同时在数据库也存上了这条记录。
@Test
public void saveUserTest() {
User user = new User();
user.setUsername("刘灰灰");
user.setBirthday(new Date());
user.setSex("男");
user.setAddress("黑龙江");
System.out.println("保存前:" + user);
System.out.println("影响的行数:" + dao.saveUser(user));
System.out.println("保存后:" + user);
}
@After
public void destroy() throws Exception{
// 提交事务
session.commit();
inputStream.close();
session.close();
}
- 我们添加一个更新方法,用来更新user对象,在UserDao接口中添加updateUser()方法。
int updateUser(User user);
- 在UserDao.xml添加如下内容,这里同我们insert一样,会默认返回影响的条数
<update id="updateUser" parameterType="entity.User">
UPDATE user set username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} WHERE id=#{id}
</update>
- 这里我要说一下,我在主配置文件已经将上述的所有映射内容注册完毕,使用的package标签,如下所示
<mappers>
<package name="dao"/>
</mappers>
- 测试我们的更新方法,即updateUser方法。
@Test
public void updateUserTest() {
User user = new User();
user.setId(61);
user.setUsername("李四");
user.setBirthday(new Date());
user.setSex("女");
user.setAddress("哈尔滨");
System.out.println("影响的条数:" + dao.updateUser(user));
System.out.println("修改后:" + dao.findById(61));
}
- 在添加UserDao接口中添加一个根据id删除用户的方法。
int deleteUser(int id);
- 在UserDao.xml映射文件中添加如下内容,删除操作同样默认返回影响条数。
<delete id="deleteUser" parameterType="int">
DELETE FROM user WHERE id=#{id}
</delete>
- 测试删除deleteUser方法。
@Test
public void deleteUserTest() {
System.out.println("删除前:" + dao.findById(61));
System.out.println("影响的条数:" + dao.deleteUser(61));
System.out.println("删除后:" + dao.findById(61));
}
- 在UserDao接口中添加一个通过用户名模糊搜索的方法。
List<User> findByName(String name);
- 在UserDao.xml映射文件中添加如下内容
<select id="findByName" parameterType="String" resultType="entity.User">
SELECT * FROM user WHERE username LIKE #{username}
</select>
- 测试模糊搜索方法findByname(),我们在传入模糊搜索的内容时,要记得拼接%通配符。因为在
@Test
public void findByNameTest() {
dao.findByName("%王%").forEach(item -> System.out.println(item));
}
- 在进行模糊搜索时,我们也可以在映射文件配置%通配符,这样我们就可以在传递参数时不用手动拼接了。我们需要将原来的占位符改成
${value}
。
<select id="findByName" parameterType="string" resultType="entity.User">
select * from user where username like '%${value}%'
</select>
- 测试结果如下,我们可以发现使用这种方式虽然不用自己在传递参数时拼接通配符了,但是根据日志我们可以知道,框架在执行模糊查询时,进行的sql拼接,没有使用preparedStatement,这样就存在这sql注入的风险了,所以我们最好还是使用上一种方式吧。
- **
#{}
与${}
的区别:通过#{}
可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。#{}可以接收简单类型或pojo属性值。如果parameterType传输单个简单类型,括号里可以是value或其他名称。而${}
是将parameterType传入的内容拼接在sql中,且不进行jdbc类型转换,${}
可以接收简单类型或pojo属性值,如果是parameterType传输单个简单类型,${}括号只能是value。如果是包装类型,括号内写对象.属性值。
- 在这里要补充一下,我们第一种方式的模糊搜索,可以在我们配置映射文件时就把通配符写上,所以我们在传递参数时就可以不用手动拼接了。
<select id="findByName" parameterType="String" resultType="entity.User">
SELECT * FROM user WHERE username LIKE "%"#{user.username}"%"
</select>
- 聚合函数查询:在UserDao接口中添加查询记录总数的方法,findTotal()。
int findTotal();
- 在User.xml映射文件中添加如下内容
<select id="findTotal" resultType="int">
SELECT COUNT(id) FROM user
</select>
- 测试findTotal方法
@Test
public void findTotalTest() {
System.out.println("总共有:" + dao.findTotal() + "条记录");
}
- 在上述的那些例子中,我们传递的都是基本数据类型或者是引用类型。现在我们要传递包装类型的对象。在entity包下创建一个QueryVo包装类。
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public QueryVo(User user) {
this.user = user;
}
}
- 在UserDao接口中添加查询方法,参数使用包装类型。
List<User> findByVo(QueryVo vo);
- 在UserDao.xml映射文件中添加如下内容,注意占位符中括号里面的写法
user.username
<select id="findByVo" resultType="entity.User" parameterType="entity.QueryVo" >
SELECT * FROM user WHERE username LIKE "%"#{user.username}"%"
</select>
- 测试findByVo方法。
@Test
public void findByVoTest() {
User user = new User();
user.setUsername("王");
QueryVo vo = new QueryVo(user);
dao.findByVo(vo).forEach(item -> System.out.println(item));
}
- resultType属性用来指定结果集的类型,它支持基本类型和实体类类型。现在我们的实体类的属性名和表的字段名一致,但如果我们实体类的属性和字段不一致,会导致我们无法实现封装。例如,我们现在将User实体的属性修改一下,如下所示,当我们调用findAll方法时,我们会发现,打印的结果只有userName的属性进行的赋值,其他的属性都是null。出现这种情况是因为mysql在windows系统中是不区分大小写的
private int userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
- 我们怎样能通过不修改实体类的属性名来实现对数据的封装呢?一种是我们可以在sql查询时,使用别名进行查询。但是如果我们查询的内容过多,起别名这种方式就显得很麻烦,所以Mybatis提供了另一种机制,resultMap,使用它可以建立查询的列名和实体类的属性名不一致时建立对应关系,从而实现对实体类的封装。
resultMap中id属性
:给定一个唯一表示,是给查询select标签引用的。resultMap中的type属性
:指定实体类的全限定列名,也就是声明,这个对应关系对应的是哪个实体类。id标签
:用于指定主键字段。result标签
:用于指定非主键字段。column属性
:用于指定数据库列名。property属性
:用于指定实体类属性名称