MyBatis实践
MyBatis简介MyBatis前身是iBatis,是一个基于Java的数据持久层/对象关系映射(ORM)框架.
初识MyBatis使用MyBatis需要在pom.xml中添加如下依赖: <code class="hljs xml"><dependency> <groupid>org.mybatis</groupid> <artifactid>mybatis</artifactid> <version>3.3.0</version></dependency><dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> <version>5.1.36</version></dependency></code>
|
|
|
---|---|
|
|
|
|
|
|
mapper映射文件名有UserDAO.xml/UserMapper.xml/User.xml等几种形式, 其一般存放在与mybatis-configuration.xml同级的mapper目录下,由于其主要作用为定义SQL语句与映射关系, 因此一般统称为mapper映射文件.
定义PO类
PO类主要作用为SQL(输入/输出)映射,通常与数据库表对应:
/** * @author jifang * @since 15/12/31 下午2:27. */public class User { private Integer id; private String name; private String password; public User() { } public User(Integer id, String name, String password) { this.id = id; this.name = name; this.password = password; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '/'' + ", password='" + password + '/'' + '}'; }}
UserDAO(Java对象)
获得SqlSession,执行SQL语句, 得到映射结果:
/** * @author jifang * @since 16/2/24 下午6:15. */public class UserDAO { private SqlSessionFactory factory; @Before public void setUp() throws IOException { String resource = "mybatis/mybatis-configuration.xml"; factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource)); } @Test public void selectUserById() { try (SqlSession session = factory.openSession()) { User user = session.selectOne("namespace.selectUserById", 1); System.out.println(user); } } @Test public void selectUserByName() { try (SqlSession session = factory.openSession()) { List users = session.selectList("namespace.selectUserByName", "student"); for (User user : users) { System.out.println(user); } } }}
Insert
Insert
mapper
INSERT INTO user(name, password) VALUES(#{name}, #{password});
UserDAO
@Testpublic void insertUser() { try (SqlSession session = factory.openSession()) { User user = new User(); user.setName("new_name1"); user.setPassword("new_password"); session.insert("namespace.insertUser", user); session.commit(); }}
自增主键返回
自增主键返回
修改mapper文件,添加
SELECT LAST_INSERT_ID(); INSERT INTO user(name, password) VALUES(#{name}, #{password});
|
|
---|---|
|
|
|
|
|
|
|
|
UserDAO
@Testpublic void insertUser() { try (SqlSession session = factory.openSession()) { System.out.println(session); User user = new User(null, "new_name", "new_password"); session.insert("namespace.insertUser", user); // 需要在commit之后才能获得自增主键 session.commit(); System.out.println(user.getId()); }}
该功能还可以通过
的useGeneratedKeys/keyProperty两个属性合作完成, 详见MyBatis文档.
Update
Update
mapper
UPDATE user SET name = #{name}, password = #{password} WHERE id = #{id};
UserDAO
@Testpublic void updateUserById() { try (SqlSession session = factory.openSession(true)) { session.update("namespace.updateUserById", new User(1, "feiqing", "ICy5YqxZB1uWSwcVLSNLcA==")); }}
Delete
Delete
mapper
DELETE FROM user WHERE id = #{id};
UserDAO
@Testpublic void deleteUserById() { try (SqlSession session = factory.openSession(true)) { session.delete("namespace.deleteUserById", 51615); }}
小结
小结
#{}/${}
#{}: 表示一个占位符号,实现向PreparedStatement占位符中设置值(#{}表示一个占位符?),自动进行Java类型到JDBC类型的转换(因此#{}可以有效防止SQL注入).#{}可以接收简单类型或PO属性值,如果parameterType传输的是单个简单类型值,#{}花括号中可以是value或其它名称. ${}: 表示拼接SQL串,通过${}可将parameterType内容拼接在SQL中而不进行JDBC类型转换,${}可以接收简单类型或PO属性值,如果parameterType传输的是单个简单类型值,${}花括号中只能是value.
虽然${}不能防止SQL注入,但有时${}会非常方便(如order by排序,需要将列名通过参数传入SQL,则用ORDER BY ${column},使用#{}则无法实现此功能(详见JDBC基础关于PreparedStatement的讨论).
SqlSession
提供操作数据库的方法(如:selectOne/selectList).但SqlSession是线程不安全的,因此最好将其定义成局部变量使用.
MyBatis优点(与JDBC相比)
SQL写在Java代码中导致不易维护, 而MyBatis将SQL写在mapper中,XML与Java代码分离. 向SQL语句传参繁琐(如:SQL的where条件不一,SQL数据类型与Java不同),MyBatis通过parameterType自动将Java对象映射至SQL语句. 结果集解析麻烦(SQL变化导致解析代码变化,SQL数据类型与Java不同),MyBatis通过resultType自动将SQL执行结果映射成Java对象.
附: 最好在pom.xml中添加一个日志系统实现(logback/log4j), 这样会在调试程序时打印日志信息,便于查错, 以logback为例:
pom.xml
ch.qos.logback logback-classic 1.1.2
logback.xml
<code class="hljs xml"><code class="hljs xml"><code class="hljs xml"><code class="hljs java"><code class="hljs java"><code class="hljs applescript"><code class="hljs java"><code class="hljs xml"><code class="hljs cs"><code class="hljs haml"><code class="hljs java"><code class="hljs livecodeserver"><code class="hljs java"><code class="hljs xml"><code class="hljs xml"><configuration> <property name="logRoot" value="/data/logs"> <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{0} - %msg%n"> <appender class="ch.qos.logback.core.ConsoleAppender" name="STDOUT"> <encoder> <pattern>${pattern}</pattern> </encoder> </appender> <appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="FILE"> <rollingpolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <filenamepattern>${logRoot}/common-server.%d{yyyy-MM-dd}.log</filenamepattern> <maxhistory>7</maxhistory> </rollingpolicy> <encoder> <pattern>${pattern}</pattern> </encoder> </appender> <root level="DEBUG"> <appender-ref ref="STDOUT"> <appender-ref ref="FILE"> </appender-ref></appender-ref></root></property></property></configuration></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code>
其他关于MyBatis日志的详细信息可参考MyBatis文档日志部分.
DAO开发
DAO开发
使用MyBatis开发DAO有两个方法,原始DAO开发与Mapper映射DAO开发.
原始DAO开发
原始DAO开发
原始DAO开发需要开发人员编写DAO接口与DAO实现,如根据ID查询用户信息:
mapper(同前)
<code class="hljs xml"><code class="hljs xml"><code class="hljs xml"><code class="hljs java"><code class="hljs java"><code class="hljs applescript"><code class="hljs java"><code class="hljs xml"><code class="hljs cs"><code class="hljs haml"><code class="hljs java"><code class="hljs livecodeserver"><code class="hljs java"><code class="hljs xml"><code class="hljs xml"><code class="hljs vbnet"><select id="selectUserById" parametertype="java.lang.Integer" resulttype="com.fq.domain.User"> SELECT * FROM user WHERE id = #{id};</select></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code>
UserDAO接口
/** * @author jifang * @since 16/2/22 上午10:20. */public interface UserDAO { User selectUserById(Integer id) throws Exception;}
UserDAO实现
public class UserDAOImpl implements UserDAO { private SqlSessionFactory factory; public UserDAOImpl(SqlSessionFactory factory) { this.factory = factory; } @Override public User selectUserById(Integer id) throws Exception { SqlSession session = factory.openSession(); User user = session.selectOne("namespace.selectUserById", id); session.close(); return user; }}
Client
public class MyBatisClient { @Test public void originalClient() throws Exception { UserDAO dao = new UserDAOImpl(new SqlSessionFactoryBuilder(). build(ClassLoader.getSystemResourceAsStream("mybatis/mybatis-configuration.xml"))); User user = dao.selectUserById(1); System.out.println(user); }}
原始DAO开发中存在的问题:
1) DAO实现方法体中存在很多过程性代码.
2) 调用SqlSession的方法(select/insert/update)需要指定Statement的id,存在硬编码,不利于代码维护.
Mapper映射开发
Mapper映射开发
mapper映射开发方法只需编写DAO接口,MyBatis根据接口定义与mapper文件中的SQL语句动态创建接口实现.
mapper
注意: 此时namespace必须与UserDAO接口的全限定名相同.
UserDAO接口与前面相同, 但不再使用UserDAOImpl Client
/** * @author jifang * @since 16/2/22 下午2:57. */public class MyBatisClient { private SqlSession session; private SqlSessionFactory factory; @Before public void setUp() { factory = new SqlSessionFactoryBuilder(). build(ClassLoader.getSystemResourceAsStream("mybatis/mybatis-configuration.xml")); session = factory.openSession(); } @Test public void mapperClient() throws Exception { UserDAO dao = session.getMapper(UserDAO.class); User user = dao.selectUserById(1); System.out.println(user); } @After public void tearDown() { session.close(); }}
mapper映射开发方法需要遵循以下规范:
mapper文件中的namespace与DAO接口的全限定名相同; mapper文件中的Statement的id与DAO接口方法名相同; mapper文件中的Statement的parameterType/resultType与DAO方法的入参/回参类型相同.
Mapper映射
Mapper映射
mapper映射文件(如UserDAO.xml)主要作用是定义SQL语句(每个SQL是一个Statement),是MyBatis的核心.
MyBatis官方推荐使用mapper映射的方法来开发DAO,因此我们以后就不再过多介绍原始DAO的开发.
输入映射
输入映射
多个形参
多个形参
传递简单类型前面示例已经使用过,在此就不再赘述.当需要传递多个形参时,不再需要设置parameterType参数:
mapper
UPDATE user SET name = #{1}, password = #{2} WHERE id = #{0};
UserDAO
void updateUserById(Integer id, String name, String password) throws Exception;
传入PO
传入PO
MyBatis使用OGNL表达式解析对象属性值:
mapper
<code class="hljs xml"><code class="hljs xml"><code class="hljs xml"><code class="hljs java"><code class="hljs java"><code class="hljs applescript"><code class="hljs java"><code class="hljs xml"><code class="hljs cs"><code class="hljs haml"><code class="hljs java"><code class="hljs livecodeserver"><code class="hljs java"><code class="hljs xml"><code class="hljs xml"><code class="hljs vbnet"><code class="hljs java"><code class="hljs java"><code class="hljs cs"><code class="hljs xml"><code class="hljs java"><code class="hljs haml"><code class="hljs lasso"><code class="hljs vbnet"><select id="selectUserByNamePassword" parametertype="com.fq.domain.User" resulttype="com.fq.domain.User"> SELECT * FROM user WHERE name = #{name} AND password = #{password};</select></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code>
UserDAO
User selectUserByNamePassword(User user) throws Exception;
传入Map
传入Map
mapper
<code class="hljs xml"><code class="hljs xml"><code class="hljs xml"><code class="hljs java"><code class="hljs java"><code class="hljs applescript"><code class="hljs java"><code class="hljs xml"><code class="hljs cs"><code class="hljs haml"><code class="hljs java"><code class="hljs livecodeserver"><code class="hljs java"><code class="hljs xml"><code class="hljs xml"><code class="hljs vbnet"><code class="hljs java"><code class="hljs java"><code class="hljs cs"><code class="hljs xml"><code class="hljs java"><code class="hljs haml"><code class="hljs lasso"><code class="hljs vbnet"><code class="hljs java"><code class="hljs vbnet"><select id="selectUserByMap" parametertype="java.util.Map" resulttype="com.fq.domain.User"> SELECT * FROM user WHERE name = #{name} AND password = #{password};</select></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code>
#{}花括号内对应Map的key.
UserDAO
User selectUserByMap(Map map) throws Exception;
输出映射
输出映射
输出简单类型
输出简单类型