test
user
domain 类
(也可以不要,直接用 Map)mapper xml
mapper interface
添加 MySQL, MyBatis, MyBatis-Spring 的依赖到 pom.xml
<!-- For MyBatis -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.1</version>
</dependency>
保存在 resources/spring-mybatis.xml
参考文档
:MyBatis - MyBatis-Spring | 简介
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1. Date source config -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<!-- 2. SQL session factory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:mybatis-mapper/**/*.xml" /> <!-- Mapper xml -->
</bean>
<!-- 3. Instantiate Mapper -->
<!--<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">-->
<!--<property name="mapperInterface" value="mapper.UserMapper" />-->
<!--<property name="sqlSessionFactory" ref="sqlSessionFactory" />-->
<!--</bean>-->
<!--没有必要在Spring的XML配置文件中注册所有的映射器。
相反, 你可以使用一个MapperScannerConfigurer,
它将会查找类路径下的映射器并自动将它们创建成MapperFactoryBean。
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="mapper" />
</bean>
</beans>
在 web.xml
中用 ContextLoaderListener
加载 spring-mybatis.xml
。
ContextLoaderListener 对应的容器是其他 Spring 容器的父容器,所以在里面创建的 MyBatis 的 mapper 在 springmvc
这个容器即以后要使用的 Shiro filter 中都能访问。
<web-app>
...
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring-mybatis.xml
</param-value>
</context-param>
...
</web-app>
用于把数据库查询到的结果映射为对象,方便使用。和查询结果的列对应就可以了,不需要和数据库的所有列一一对应。
domain.User 在前面的例子里已经创建好了,如果没有,则创建。
package domain;
import org.hibernate.validator.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class User {
private int id;
private String username;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NotBlank(message="用户名不能为空") // 进行参数验证
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@NotNull(message="密码不能为null") // 进行参数验证
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
保存在 resources/mybatis-mapper/User.xml
<?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">
<!--namespace 非常重要:必须是 Mapper 类的全路径-->
<mapper namespace="mapper.UserMapper">
<!-- [1] 简单的 JavaBean,直接使用 resultType: 数据库表的列与 JavaBean 的属性对应 -->
<select id="findUserById" parameterType="int" resultType="domain.User">
SELECT id, username, password FROM user WHERE id = #{id}
</select>
<select id="findUsers" resultType="domain.User">
SELECT id, username, password FROM user LIMIT ${offset}, ${count}
</select>
</mapper>
创建接口 mapper.UserMapper
,只是定义一下接口,不需要我们提供它的实现,这个接口的实现会由 MyBatis-Spring 的框架创建。
package mapper;
import domain.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
public User findUserById(int id);
// 使用 @Param 的方式传参数
public List<User> findUsers(@Param("offset") int offset, @Param("count") int count);
}
类 controller.MyBatisController 用于测试使用 MyBatis 访问数据库。
package controller;
import domain.User;
import mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controller
public class MyBatisController {
@Autowired
private UserMapper userMapper;
@RequestMapping("/user/{userId}")
@ResponseBody
public String findUser(@PathVariable Integer userId) {
// 查找一个 User
User user = userMapper.findUserById(userId);
List<User> users = userMapper.findUsers(1, 2);
System.out.println(users);
return user.toString();
}
}
控制台输出的 MyBatis 的 Debug 信息
[2015-04-06 14:09:15] [DEBUG] [BaseJdbcLogger.java-debug:132] - ooo Using Connection [jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8, UserName=root@localhost, MySQL-AB JDBC Driver]
[2015-04-06 14:09:15] [DEBUG] [BaseJdbcLogger.java-debug:132] - ==> Preparing:SELECT id, username, password FROM user WHERE id = ?
[2015-04-06 14:09:15] [DEBUG] [BaseJdbcLogger.java-debug:132] - ==>Parameters: 1(Integer)
[2015-04-06 14:09:16] [DEBUG] [BaseJdbcLogger.java-debug:132] - ooo Using Connection [jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8, UserName=root@localhost, MySQL-AB JDBC Driver]
[2015-04-06 14:09:16] [DEBUG] [BaseJdbcLogger.java-debug:132] - ==> Preparing:SELECT id, username, password FROM user LIMIT 1, 2
[2015-04-06 14:09:16] [DEBUG] [BaseJdbcLogger.java-debug:132] - ==> Parameters:
[User{id=2, username='黄彪', password='Pa88w0rd'}, User{id=3, username='Alice', password='xxxxxxxx'}]
可以看到查询的 SQL 语句,查询用的参数等,可以在 Logback 的配置中设置 MyBatis 的日志级别,如 <logger name="org.mybatis" level="debug"/>
,以便看到更多信息。
到此看看工程的目录结构
MySql:
SELECT * FROM user WHERE name like CONCAT('%',#{name},'%')
Oracle:
SELECT * FROM user WHERE name like CONCAT('%',#{name},'%') 或
SELECT * FROM user WHERE name like '%'||#{name}||'%'
SQLServer:
SELECT * FROM user WHERE name like '%'+#{name}+'%'
DB2:
SELECT * FROM user WHERE name like CONCAT('%',#{name},'%') 或
SELECT * FROM user WHERE name like '%'||#{name}||'%'
查看 mapper.UserMapper
public List<User> findUsers(@Param("offset") int offset, @Param("count") int count);
#{name}
会根据传进来的参数的类型自动加上相应的信息,例如字符串两边会加上 ''
,日期对象会自动的转化成 SQL 识别的内容,可以防止 SQL 注入攻击
。
${name}
直接替换,例如传进来的是字符串,不会在字符串两边加上 ''
,比较适合 int 等类型,例如分页时的 offset and count。
参考 8 中控制台输出的 SQL 语句。
MyBatis 推荐使用连接池 DBCP
,Hibernate 推荐使用连接池 C3P0
。
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
修改 resources/spring-mybatis.xml 里的 <bean id="dataSource" ...>
为下面的内容
<!-- 1. Data Source using DBCP. -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8" />
<property name="username" value="root" />
<property name="password" value="root" />
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="10" />
<!-- 连接池的最大值 -->
<property name="maxActive" value="100" />
<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="maxIdle" value="50" />
<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
<property name="minIdle" value="5" />
<!--#给出一条简单的sql语句进行验证-->
<property name="validationQuery" value="select NOW()" />
<!--#在取出连接时进行有效验证-->
<property name="testOnBorrow" value="false" />
<property name="testWhileIdle" value="true" />
<property name="logAbandoned" value="true" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="120" />
<!-- #运行判断连接超时任务的时间间隔,单位为毫秒,默认为-1,即不执行任务。 -->
<property name="timeBetweenEvictionRunsMillis" value="3600000" />
<!-- #连接的超时时间,默认为半小时。 -->
<property name="minEvictableIdleTimeMillis" value="3600000" />
</bean>
用命令 SHOW FULL PROCESSLIST
可以看到 MySQL 里的连接数
从上图可以看到我们的程序创建了 10 个连接(15到24),和配置里初始化时创建 10 个连接正好匹配。多次访问 http://localhost/user/1 可以发现某些连接的 Time
会变小(不活动时间),说明连接被重复的使用,而不是新创建的。
<?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" >
<mapper namespace="com.tur.mapper.UserMapper" >
<sql id="columns" > id, age, name</sql>
<!-- [[1]] 简单的JavaBean,直接使用resultType: 数据库表的列与JavaBean的属性对应 -->
<select id="selectUserById" parameterType="int" resultType="com.tur.domain.User" >
SELECT <include refid="columns"/>
FROM user WHERE id = #{id}
</select>
<select id="selectUsersByName" parameterType="string" resultType="com.tur.domain.User" >
SELECT <include refid="columns"/>
FROM user WHERE name = #{name}
</select>
<!-- [[2]] 可以使用resultMap映射自己的类: 例如多表查询时 -->
<select id="selectUserById" parameterType="int" resultMap="userResultMap" >
SELECT <include refid="columns"/>
FROM user WHERE id = #{id}
</select>
<resultMap id="userResultMap" type="com.tur.domain.User" >
<id property="id" column="id"/>
<result property="age" column="age"/>
<result property="name" column="name"/>
</resultMap>
<!-- [[3]] 使用resultMap映射,属性是另一个类的对象: association -->
<select id="selectFullUserById" parameterType="int" resultMap="userAssociationResultMap" >
SELECT
user.id as id, <!-- 重命名列非常有用 -->
user.age as age,
user.name as name,
ui.id as user_info_id,
ui.user_id as user_info_user_id,
ui.telephone as user_info_telephone,
ui.address as user_info_address
<!--FROM user, user_info ui-->
FROM user
INNER JOIN user_info ui ON user.id=ui.user_id
WHERE user.id=#{id}
<!--AND user.id=ui.user_id-->
</select>
<resultMap id="userAssociationResultMap" type="com.tur.domain.User" >
<id property="id" column="id"/>
<result property="age" column="age"/>
<result property="name" column="name"/>
<!--嵌套映射中还可以使用resultMap: association, collection
还可以使用嵌套查询,但是会产生N+1问题,在大数量的数据库里会有很大的性能问题-->
<!--<association property="userInfo" column="user_info_id" javaType="domain.UserInfo">
<id property="id" column="user_info_id"/>
<result property="userId" column="user_info_user_id"/>
<result property="telephone" column="user_info_telephone"/>
<result property="address" column="user_info_address"/>
</association>-->
<!--association是一对一关系,collection是一对多关系-->
<!--使用columnPrefix可以使result map重用-->
<association property="userInfo" column="user_info_id" columnPrefix="user_info_" resultMap="userInfoResultMap"/>
</resultMap>
<resultMap id="userInfoResultMap" type="com.tur.domain.UserInfo" >
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="telephone" column="telephone"/>
<result property="address" column="address"/>
</resultMap>
<select id="selectUsersWithName" parameterType="list" resultType="com.tur.domain.User" >
SELECT <include refid="columns"/>
FROM user
WHERE name in
<foreach item="item" index="index" open="("separator=","close=")" collection="list" >
#{item}
</foreach>
</select>
</mapper>