为了方便测试,分别增加@Before 和@After,代码如下:
public class MyBatisTest { private SqlSession sqlSession; private UserMapper userMapper; /*** * 在执行@Test之前执行 */ @Before public void init(){ try { //1.读取主配置文件(SqlMapConfig.xml),获取配置文件的字节输入流 InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建一个SqlSessionFactoryBuilder对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3.构建SqlSessionFactory(会话工厂对象) SqlSessionFactory sqlSessionFactory = builder.build(is); //4.打开一个SqlSession,拥有Connection的作用 sqlSession = sqlSessionFactory.openSession(); //5.通过SqlSession获取对应Dao的代理类 userMapper = sqlSession.getMapper(UserMapper.class); } catch (IOException e) { e.printStackTrace(); } } /*** * 查询所有 */ @Test public void testFindAll() throws Exception { //调用对应Dao实现数据库增删改查 List
users = userMapper.findAll(); for (User user : users) { System.out.println(user); } } /**** * @Test执行完成之后执行 */ @After public void destroy(){ //7.关闭资源 sqlSession.close(); } }
1. 在UserMapper类中添加新增方法
为了实现新增操作,我们可以在原有入门示例的UserMapper.java类中添加一个用于saveUser()的方法用于用户新增操作。
UserMapper类中新增saveUser()方法,如下:
/** * 添加用户 * @param user * @return */ int saveUser(User user);
2.在UserMapper.xml文件中加入新增配置
在UserMapper.xml文件中加入新增用户的配置,如下:
INSERT INTO user(username,birthday,sex,address)VALUES(#{username},#{birthday},#{sex},#{address})
我们可以发现,这个sql语句中使用#{}字符,#{}代表占位符,我们可以理解是原来jdbc部分所学的?,它们都是代表占位符, 具体的值是由User类的username属性来决定的。
parameterType属性:代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。
注意:这种方式要求 ,同时还要求< select>,< insert>,< delete>,< update>这些标签中的id属性一定与代理接口中的方法名相同。
3.添加测试类中的测试方法
/*** * 保存用户 */ @Test public void testSaveUser(){ User user = new User(); user.setUsername("张三"); user.setSex("男"); user.setBirthday(new Date()); user.setAddress("深圳"); userMapper.saveUser(user); }
4.测试结果如下:
==> Preparing: INSERT INTO user(username,birthday,sex,address)VALUES(?,?,?,?) ==> Parameters: 张三(String), 2018-08-01 15:19:50.543(Timestamp), 男(String), 深圳(String) <== Updates: 1
打开Mysql数据库发现并没有添加任何记录,原因是什么?
这一点和jdbc是一样的,我们在实现增删改时一定要去控制事务的提交,那么在mybatis中如何控制事务提交呢?
可以使用:session.commit();来实现事务提交。加入事务提交后的代码如下:
/**** * @Test执行完成之后执行 */ @After public void destroy(){ sqlSession.commit(); sqlSession.close(); }
5.问题扩展:新增用户id的返回值
新增用户后,同时还要返回当前新增用户的id值,因为id是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长auto_increment的值返回。
Mysql自增主键的返回,配置如下:
SELECT LAST_INSERT_ID() INSERT INTO user(username,birthday,sex,address)VALUES(#{username},#{birthday},#{sex},#{address})
1.在UserMapper类中添加更新方法
为了实现更新操作,我们可以在原有入门示例的UserMapper.java类中添加一个用于updateUser()的方法用于用户更新操作。
UserMapper类中新增updateUser()方法,如下:
/*** * 修改用户 * @param user * @return */ int updateUser(User user);
2.在UerMapper.xml文件中加入更新操作配置
在UserMapper.xml文件中加入更新用户的配置,如下:
UPDATE user SET username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} WHERE id=#{id}
上面的更新语句中,同样使用#{}代表占位符,比如#{username}代表占位符,将来这个部分使用User对象中的username属性的值来作为sql语句中username字段的值。
3.加入更新的测试方法
/***
* 修改用户 */ @Test public void testUpdateUser(){ User user = new User(); user.setUsername("张飞"); user.setSex("男"); user.setBirthday(new Date()); user.setAddress("天津"); user.setId(61); userMapper.updateUser(user); }
4.测试结果:
==> Preparing: UPDATE user SET username=?,birthday=?,sex=?,address=? WHERE id=? ==> Parameters: 张飞(String), 2018-08-01 15:55:02.267(Timestamp), 男(String), 天津(String), 61(Integer) <== Updates: 1
1.在UserMapper类中加入删除方法
在UserMapper类中加入删除deleteUser()方法,用于实现用户的删除。
/** * 根据ID删除用户 * @param id * @return */ int deleteUser(Integer id);
2.在UserMapper.xml文件中加入删除操作
加入的删除的映射配置信息如下:
DELETE FROM user WHERE id=#{id} 其中的#{id}是占位符,代表参数的值由方法的参数传入进来的。 注意: 1.此处的#{id}中的id其实只是一个形参,所以它的名称是自由定义的,比如定义成#{abc}也是可以的。 2.关于parameterType的取值问题,对于基本类型我们可以直接写成int,short,double…..也可以写成java.lang.Integer。 3.字符串可以写成string,也可以写成java.lang.String
3.加入删除的测试方法
在原有的测试类中加入测试方法,如下:
/***
* 根据ID删除用户 */ @Test public void testDeleteUser(){ userMapper.deleteUser(61); }
4.下面是测试的结果:
==> Preparing: DELETE FROM user WHERE id=? ==> Parameters: 61(Integer) <== Updates: 1
现在来实现根据用户名查询用户信息,此时如果用户名想用模糊搜索的话,我就可以想到前面Web课程中所学的模糊查询来实现。
1.在UserMapper类中添加模糊查询方法
可以在UserMapper类中添加一个findUserByUsername()的方法,如下:
/*** * 模糊查询 * @param username * @return */ List
findUserByUsername(String username);
2.在UserMapper.xml文件中加入模糊查询的配置
下面在UserMapper.xml文件中加入模糊查询的配置代码,如下:
注意:此时的#{username}中的因为这时候是普通的参数,所以它的起名是随意的,比如我们改成#{abc}也是可以的。
3.加入模糊查询的测试方法
在测试类中加入模糊测试的testFindUserByUsername()方法,如下:
@Test public void testFindUserByUsername(){ String name="%张%"; List
users = userMapper.findUserByUsername(name); for (User user : users) { System.out.println(user); } }
4.在控制台输出的执行SQL语句如下:
==> Preparing: SELECT * FROM user WHERE username LIKE ? ==> Parameters: %张%(String) <== Total: 1 User{id=42, username='张三', birthday=Fri Mar 02 15:09:37 CST 2018, sex='女', address='北京金燕龙'}
我们在UserMapper.xml配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%。配置文件中的#{username}也只是一个占位符,所以SQL语句显示为“?”。如何将模糊查询的匹配符%写到配置文件中呢?
1. 编写UserMapper.xml文件,配置如下:
我们在上面将原来的#{}占位符,改成了${value}。注意如果用模糊查询的这种写法,那么${value}的写法就是固定的,不能写成其它名字。
2. 编写测试方法,如下:
@Test public void testFindUserByUsername(){ String name="张"; List
users = userMapper.findUserByUsername(name); for (User user : users) { System.out.println(user); } }
3. 在控制台输出的执行SQL语句如下:
==> Preparing: SELECT * FROM user WHERE username LIKE '张' ==> Parameters: <== Total: 0
可以发现,我们在程序代码中就不需要加入模糊查询的匹配符%了,这两种方式的实现效果是一样的,但执行的语句是不一样的。
井{}表示一个占位符号
- 通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
- ${}表示拼接sql串
- 通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。
我们在将模糊查询的匹配符%写到配置文件时,就用到一个固定写法${value},如下匹配:
那么为什么一定要写成${value}呢?我们一起来看TextSqlNode类的源码:
@Override
public String handleToken(String content) { Object parameter = context.getBindings().get("_parameter"); if (parameter == null) { context.getBindings().put("value", null); } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) { context.getBindings().put("value", parameter); } Object value = OgnlCache.getValue(content, context.getBindings()); String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null" checkInjection(srtValue); return srtValue; }
Mybatis默认情况下支持参数写别名,这就导致了我们前面在看基本类型时,既可以写成int,也可以写成java.lang.Integer,默认支持的到底有哪些类型?
从源码角度怎么去找?这些都是支持的默认别名。我们也可以从源码角度来看它们分别都是如何定义出来的。可以参考TypeAliasRegistery.class的源码。
package org.apache.ibatis.type; public class TypeAliasRegistry { public TypeAliasRegistry() { registerAlias("string", String.class); registerAlias("byte", Byte.class); registerAlias("long", Long.class); registerAlias("short", Short.class); registerAlias("int", Integer.class); registerAlias("integer", Integer.class); registerAlias("double", Double.class); registerAlias("float", Float.class); registerAlias("boolean", Boolean.class); registerAlias("byte[]", Byte[].class); registerAlias("long[]", Long[].class); registerAlias("short[]", Short[].class); registerAlias("int[]", Integer[].class); registerAlias("integer[]", Integer[].class); registerAlias("double[]", Double[].class); registerAlias("float[]", Float[].class); registerAlias("boolean[]", Boolean[].class); registerAlias("_byte", byte.class); registerAlias("_long", long.class); registerAlias("_short", short.class); registerAlias("_int", int.class); registerAlias("_integer", int.class); registerAlias("_double", double.class); registerAlias("_float", float.class); registerAlias("_boolean", boolean.class); registerAlias("_byte[]", byte[].class); registerAlias("_long[]", long[].class); registerAlias("_short[]", short[].class); registerAlias("_int[]", int[].class); registerAlias("_integer[]", int[].class); registerAlias("_double[]", double[].class); registerAlias("_float[]", float[].class); registerAlias("_boolean[]", boolean[].class); registerAlias("date", Date.class); registerAlias("decimal", BigDecimal.class); registerAlias("bigdecimal", BigDecimal.class); registerAlias("biginteger", BigInteger.class); registerAlias("object", Object.class); registerAlias("date[]", Date[].class); registerAlias("decimal[]", BigDecimal[].class); registerAlias("bigdecimal[]", BigDecimal[].class); registerAlias("biginteger[]", BigInteger[].class); registerAlias("object[]", Object[].class); registerAlias("map", Map.class); registerAlias("hashmap", HashMap.class); registerAlias("list", List.class); registerAlias("arraylist", ArrayList.class); registerAlias("collection", Collection.class); registerAlias("iterator", Iterator.class); registerAlias("ResultSet", ResultSet.class); } //略..... }
1)数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。
2)Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3)向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
4)对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
这就说明了源码中指定了读取的key的名字就是”value”,所以我们在绑定参数时就只能叫value的名字了。