转载

Mybatis学习笔记(一)——基本的CRUD操作

MyBatis是什么

mybatis 是托管在github上的ORM框架,让程序员将主要精力放在 SQL 上,通过mybatis提供映射方式,自由灵活(SQL的可定制性较高,半自动化)生成满足需求的SQL语句。mybatis可以将向 preparedStatement中的输入参数自动进行 输入映射 ,将查询结果集灵活映射成java对象。( 输出映射

在进行项目的编码之前首先初始化数据库,项目所需要的 sql脚本 。

原生jdbc中的问题总结

观察以下的代码:

@Test public void testJDBC(){  final String DB_DRIVER = "org.gjt.mm.mysql.Driver";  final String DB_URL = "jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=utf-8";  final String DB_USER = "root";  final String DB_PASSWORD = "mysqladmin";  Connection conn = null;  PreparedStatement pstmt = null;  ResultSet rs = null;  try {   Class.forName(DB_DRIVER);   conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);   String sql = "SELECT * FROM user WHERE username = ?";   pstmt = conn.prepareStatement(sql);   pstmt.setString(1, "王五");   rs = pstmt.executeQuery();   while (rs.next()) {    System.out.println("id:" + rs.getInt("id") + ",username:" + rs.getString("username"));   }  } catch (ClassNotFoundException | SQLException e) {   e.printStackTrace();  } finally {   if(rs != null){    try {     rs.close();    } catch (SQLException e) {     e.printStackTrace();    }   }   if( pstmt != null){    try {     pstmt.close();    } catch (SQLException e) {     e.printStackTrace();    }   }   if(conn!=null){    try {     conn.close();    } catch (SQLException e) {     e.printStackTrace();    }   }  } } 

以上的程序中存在以下的问题:

  • 数据库使用时打开连接,不使用的时候 立即 释放连接。对数据库进行了频繁的打开和关闭,影响性能(设想:可以使用DBPool)。

  • 将SQL语句 硬编码 到java代码中,修改SQL语句需要重新编译java代码(设想:使用配置文件配置SQL)。

  • 在向PreparedStatement中设置参数的时候参数的位置和参数值 硬编码 在代码中(设想:配置文件)。

  • 从结果集中遍历数据的时候存在 硬编码 。(设想:把结果集映射成javabean)

MyBatis原理

  • SqlMapConfig.xml:MyBatis全局配置文件(数据源、事务等mybatis运行环境),不是固定名称;

  • mapper.xml:映射关系(配置SQL语句);

  • SqlSessionFactory:创建会话(SqlSession);

  • SqlSession:操作DB(CRUD),面向用户的接口;

  • Excutor:执行器(SqlSession内部通过执行器操作DB),接口(有2个实现类,基本执行器和缓存执行器);

  • mappedstatement:底层封装对象(sql语句、输入参数、结果类型)。

Mybatis学习笔记(一)——基本的CRUD操作

入门程序

需求:

  • 根据id(主键)查询用户信息

  • 根据用户名模糊查询用户信息

  • 添加用户

  • 更新用户

  • 删除用户

在新建一个源代码目录config,在config目录下使用以下的log4j属性文件(可以从mybatis示例程序中拷贝):

编码前的准备

log4j

# 开发环境中日志的级别使用DEBUG,生产环境中日志级别为ERROR ### 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

工程目录如下所示

Mybatis学习笔记(一)——基本的CRUD操作

在SqlMapConfig.xml中配置MyBatis的运行环境:

<?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>  <!-- 与Spring整合后该配置将会被移除 -->  <environments default="development">   <environment id="development">    <!-- 使用JDBC事务进行管理,事务的控制由mybatis管理 -->    <transactionManager type="JDBC">     <property name="" value="" />    </transactionManager>    <!-- 数据库连接池,由mybatis管理 -->    <dataSource type="UNPOOLED">     <property name="driver" value="org.gjt.mm.mysql.Driver" />     <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" />     <property name="username" value="root" />     <property name="password" value="mysqladmin" />    </dataSource>   </environment>  </environments> </configuration> 

根据id查询用户信息

1.新建一个pojo名称为User,其字段如下:

private int id; private String username; private String sex; private Date birthday; private String address;

2.在映射文件(User.xml,这是原始的ibatis映射方式,如果采用mapper代理则名称应该是UserMapper.xml)中配置sql语句。

config/sqlmap 目录下新建一个 User.xml (输入的参数类型和输出的结果集映射的javabean)内容如下:

<?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"> <!-- 命名空间用来对SQL进行分类管理(SQL隔离)  注意:   如果采用Mapper代理模式开发,该命名空间具有重要意义  -->  <mapper namespace="test"> <!-- 在映射文件中配置SQL语句 --> <!-- 此处的id用来标识映射文件中的SQL,为Statement的id,将SQL语句封装到mappedstatement对象中--> <!-- 通过id查询用户    parameterType   指定输入参数的类型(id是int)   #{}    就是jdbc中的占位符   #{id}     其中的id是参数名,如果输入的参数是简单类型,#{}中的参数可以任意   resultType    指定SQL语句结果集所映射的JavaBean --> <select id="findUserById" parameterType="int" resultType="org.gpf.po.User">  SELECT * FROM user WHERE id = #{id}; </select> </mapper> 

3.在SqlMapConfig.xml中加载User.xml.(在User.xml的configuration中加入以下内容)

<mappers>     <mapper resource="sqlmap/User.xml"/> </mappers>

4.编写测试类:

/**  * 根据id查询用户信息  */ @Test public void testFindUserById() throws IOException{  // mybatis配置  String resource = "SqlMapConfig.xml";  // 得到配置文件的流  InputStream is = Resources.getResourceAsStream(resource);  // 创建会话工厂(传入配置信息)  SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);  // 通过会话工厂得到Sqlsession  SqlSession session = sqlSessionFactory.openSession();  // 查询  // 参数一:命名空间下的SQL的id;  // 参数二:和映射文件中匹配的parameterType参数  // 返回值:映射文件中resultType配置的javabean  User user = session.selectOne("test.findUserById", 1);  System.out.println(user);  // 释放资源(关闭会话)  session.close(); } 

根据用户信息模糊查询用户信息

在User.xml中配置如下:

<!-- 根据用户名进行模糊查询     注意:虽然查询的结果可能是多条,但是resultType还是javabean,即:单条记录所对应的java对象  --> <select id="findUserByName" parameterType="java.lang.String" resultType="org.gpf.po.User">     SELECT * FROM user WHERE username LIKE #{value}; </select>

测试类:

/**  * 模糊查询(可能返回多条)  */ @Test public void testFindUserByName() throws IOException {  // 通过会话工厂得到Sqlsession  SqlSession session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")).openSession();  // 返回多条记录  List users = session.selectList("test.findUserByName", "%明%");  System.out.println(users);  session.close(); } 

在执行模糊查询的时候我们在编码的时候传入了%作为参数,为了避免出错(少加了%)我们可以在User中配置以下的SQL(同样:我们需要注意: 拼接SQL语句可能导致SQL注入 ):

<!-- 根据用户名进行模糊查询     ${}            拼接SQL串,可能引起SQL注入     ${value}       接收输入的参数。如果是简单类型,只能使用value  --> <select id="findUserByName" parameterType="java.lang.String" resultType="org.gpf.po.User">     <!-- 使用${}进行SQL拼接 -->     SELECT * FROM user WHERE username LIKE '%${value}%'; </select>

在测试代码中我们可以这样:

List users = session.selectList("test.findUserByName", "明");

因为${value}进行SQL拼接可能导致sql注入因此不建议使用。

添加用户

在User.xml中配置Statement如下:

<!-- 添加用户     注意:此时输入的参数名是POJO     #{}            指定POJO的属性值(mybatis通过OGNL来获取属性值)  --> <insert id="insertUser" parameterType="org.gpf.po.User">     INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address}); </insert>

测试类:

/**  * 添加用户  */ @Test public void testInsertUser() throws IOException{  // 得到sqlsession  SqlSession session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")).openSession();  // 实例化javabean  User user = new User();  user.setUsername("萧晚晴");  user.setBirthday(new Date());  user.setSex("2");  user.setAddress("峨眉山");  // 执行插入语句,并提交事务  session.insert("test.insertUser",user);  session.commit();  // 关闭sqlsession  session.close(); } 

自增主键的返回

MySQL自增主键,执行insert提交之前自动生成一个自增主键,通过其 LAST_INSERT_ID() 函数可以获得其插入后的自增主键。修改以上的插入用户的statement:

<insert id="insertUser" parameterType="org.gpf.po.User">  <!-- 将INSERT插入记录后的主键返回,SELECT  LAST_INSERT_ID()仅适用于自增主键   keyProperty   将查询到的主键值设置到parameterType指定对象的特定属性   order    执行顺序(相对于INSERT语句来说)   resultType   指定LAST_INSERT_ID()的结果类型  -->  <selectKey keyProperty="id" order="AFTER" resultType="int">    SELECT  LAST_INSERT_ID();  </selectKey>  INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address}); </insert> 

这样我们就可以在执行插入之后取得刚刚插入的记录的主键:

/**  * 添加用户  */ @Test public void testInsertUser() throws IOException{  // 得到sqlsession  SqlSession session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")).openSession();  // 实例化javabean  User user = new User();  user.setUsername("萧晚晴");  user.setBirthday(new Date());  user.setSex("2");  user.setAddress("峨眉山");  // 执行插入语句,并提交事务  session.insert("test.insertUser",user);  session.commit();  System.out.println(user.getId()); // 得到插入数据的主键  // 关闭sqlsession  session.close(); } 

非自增主键的返回

可以使用MySQL的 uuid() 函数来生成主键,需要修改表的id字段为String,长度设置为35位。执行思路是先通过 uuid() 查询到主键,将主键输入到SQL中。注意:执行的 uuid() 函数的顺序相对于INSERT语句之前执行。

<!-- 使用MySQL的uuid()返回主键,该方法适用于非自增主键--> <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">      SELECT  UUID(); </selectKey> INSERT INTO user(id,username,birthday,sex,address) VALUES(#{id},#{username},#{birthday},#{sex},#{address});

如果使用的是Oracle,序列就类似于MySQL的 uuid() 函数,替换即可:

<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">      SELECT 序列名.nextval(); </selectKey> INSERT INTO user(id,username,birthday,sex,address) VALUES(#{id},#{username},#{birthday},#{sex},#{address});

删除用户

映射文件:

<!-- 删除用户 --> <delete id="deleteUser" parameterType="java.lang.Integer">     DELETE FROM user WHERE id = #{id} </delete>

测试类:

/**  * 根据id删除用户  */ @Test public void testDeleteUser() throws IOException{  // 得到sqlsession  SqlSession session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")).openSession();  // 执行删除语句,并提交事务  session.delete("test.deleteUser", 31);  session.commit();  // 关闭sqlsession  session.close(); } 

更新用户信息

映射文件

<!-- 更新用户信息      因为更新用户信息需要指定用户的id和用户的信息,所以parameterType为javabean(注意id必须存在否则将全部更新)     #{id}          从输入的user对象中获取id属性值 --> <update id="updateUser" parameterType="org.gpf.po.User">     UPDATE user SET username = #{username},birthday=#{birthday},sex=#{sex},address=#{address} WHERE id = #{id} </update>

测试程序

/**  * 更新用户  */ @Test public void testupdateUser() throws IOException{  // 得到sqlsession  SqlSession session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")).openSession();  // 实例化javabean  User user = new User();  user.setId(32);  user.setUsername("上官飞燕");  user.setBirthday(new Date());  user.setSex("1");  user.setAddress("青城山");  // 执行更新语句,并提交事务  session.update("test.updateUser",user);  session.commit();  // 关闭sqlsession  session.close(); } 

总结

  • parameterType在映射文件中通过parameterType指定输入参数的类型。

  • resultType在映射文件中通过resultType指定输出结果的类型。

  • #{} 和${}

#{} 表示一个占位符号, #{} 接收输入参数,类型可以是简单类型,pojo、hashmap。如果接收简单类型,#{}中可以写成value或其它名称。

#{} 接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性...的方式获取对象属性值。

${}表示一个拼接符号,会引用sql注入,所以不建议使用${}。

${}接收输入参数,类型可以是简单类型,pojo、hashmap。如果接收简单类型,${}中只能写成value。

${}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性...的方式获取对象属性值。

  • selectOne和selectList

    • selectOne表示查询出一条记录进行映射。如果使用selectOne可以实现使用selectList也可以实现(list中只有一个对象)。

    • selectList表示查询出一个列表(多条记录)进行映射。如果使用selectList查询多条记录,不能使用selectOne。

如果使用selectOne报错:

org.apache.ibatis.exceptions.TooManyResultsException: Expected oneresult (or null) to be returned by selectOne(), but found: 4

正文到此结束
Loading...