文章目录
1 SqlSession生命周期
- sqlsession:方法级别
- sqlsessionFactory:全局范围(应用级别)
- sqlsessionFactoryBuilder:方法级别
1.1 SqlSessionFactoryBuilder
SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围(即方法体内局部变量)。
1.2 SqlSessionFactory
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。
1.3 SqlSession
SqlSession中封装了对数据库的操作,如查询、插入、更新、删除等。通过SqlSessionFactory创建SqlSession,而SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建的。
SqlSession是一个面向程序员的接口,SqlSession中定义了数据库操作方法,所以SqlSession作用是操作数据库,并且SqlSession对象要存储数据库连接、事务和一级缓存结构等。
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它是线程不安全的(多线程访问系统,当多线程同时使用一个SqlSession对象时会造成数据冲突问题)。由于SqlSession对象是线程不安全的,因此它的最佳使用范围是请求或方法范围(也可说为SqlSession的最佳使用场合是在方法体内作为局部变量来使用),绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
打开一个SqlSession,使用完毕就要关闭它。通常把这个关闭操作放到finally块中以确保每次都能执行关闭。如下:
SqlSession session = sqlSessionFactory.openSession();
try {
} finally {
session.close();
}
2 原始Dao开发方式
原始Dao开发方法需要程序员自己编写Dao接口和Dao实现类。
2.1 UserMapper.xml和SqlMapConfig.xml
UserMapper.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">
<mapper namespace="user">
<select id="findUserById" parameterType="int" resultType="User">
SELECT * from user WHERE id=#{id}
</select>
<insert id="addUser" parameterType="User">
<!-- selectKey将主键返回,需要再返回 -->
<selectKey keyProperty="id" order="AFTER" resultType="int">
select LAST_INSERT_ID()
</selectKey>
INSERT INTO user(username,password,email,phone,address)
VALUES (#{username},#{password},#{email},#{phone},#{address})
</insert>
<select id="getUserByName" parameterType="String" resultType="user">
SELECT * FROM user where username=#{username}
</select>
</mapper>
SqlMapConfig.xml
<?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>
<properties resource="db.properties"></properties>
<typeAliases>
<typeAlias type="pojo.User" alias="user"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml" />
</mappers>
</configuration>
2.2 UserDao和UserDaoImp
public interface UserDao {
User findUserbyId(int id);
void addUser(User user);
List<User> getUserByName(String username);
}
UserDaoImpl.java:
public class UserDaoImp implements UserDao {
private SqlSessionFactory sqlSessionFactory;
public UserDaoImp(SqlSessionFactory sqlSessionFactory){
this.sqlSessionFactory =sqlSessionFactory;
}
public User findUserbyId(int id) {
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("findUserById",id);
return user;
}
public void addUser(User user) {
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.insert("addUser", user);
sqlSession.commit();
if (sqlSession!=null)
sqlSession.close();
}
public List<User> getUserByName(String username) {
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> users =sqlSession.selectList("getUserByName",username);
return users;
}
}
2.2 测试
public class UserDaoTest {
private SqlSessionFactory sqlSessionFactory = null; // 工厂对象一般在我们的系统中是单例的
@Before
public void init() throws IOException {
// 第一步,创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 第二步,加载配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 第三步,创建SqlSessionFactory对象
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
}
@Test
public void testFindUserById(){
UserDao userDao = new UserDaoImp(sqlSessionFactory);
User u =userDao.findUserbyId(1);
System.out.println(u);
}
@Test
public void testAddUser(){
UserDao userDao = new UserDaoImp(sqlSessionFactory);
User user = new User("小xiao小", "111", "1@com", "123", "sasas");
userDao.addUser(user);
}
@Test
public void testGetUserByName(){
UserDao userDao = new UserDaoImp(sqlSessionFactory);
List<User> users =userDao.getUserByName("小小");
for (User u:users)
System.out.println(u);
}
}
2.3 原始Dao开发方式所带来的问题
从以上UserDaoImpl类的代码可看出原始Dao开发存在以下问题:
- dao接口实现类方法中存在大量的重复代码,这些重复的代码就是模板代码。
模板代码为:
先创建sqlsession
再调用sqlsession的方法
再提交sqlsession
再关闭sqlsession
设想能否将这些代码提取出来,这可大大减轻程序员的工作量。 - 调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不得于开发维护。
- 调用sqlsession方法时传入的变量,由于sqlsession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。
3 Mapper动态代理开发方式
设计模式——动态代理
Mapper接口开发方法只需要程序员编写Mapper接口,由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
3.1 Mapper接口开发需要遵循以下规范:
Mapper.xml文件中的namespace与mapper接口的类路径相同,即namespace必须是接口的全限定名。
Mapper接口方法名和Mapper.xml中定义的每个statement的id相同。
Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同。
Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
3.2 编写Mapper.xml(映射文件)
我们可在config源码目录下新建一个mapper的普通文件夹,该文件夹专门用于存放映射文件。然后在该文件夹下创建一个名为mapper.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">
<mapper namespace="mapper.UserMapper">
<select id="findUserById" parameterType="int" resultType="User">
SELECT * from user WHERE id=#{id}
</select>
<insert id="addUser" parameterType="User">
<!-- selectKey将主键返回,需要再返回 -->
<selectKey keyProperty="id" order="AFTER" resultType="int">
select LAST_INSERT_ID()
</selectKey>
INSERT INTO user(username,password,email,phone,address)
VALUES (#{username},#{password},#{email},#{phone},#{address})
</insert>
<select id="getUserByName" parameterType="String" resultType="user">
SELECT * FROM user where username=#{username}
</select>
</mapper>
3.3 编写Mapper接口
创建一个Mapper接口——UserMapper.java:
public interface UserMapper {
User findUserById(Integer id);
void addUser(User user);
List<User> getUserByName(String username);
}
3.4 SqlMapConfig.xml
<?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>
<properties resource="db.properties"></properties>
<typeAliases>
<!-- <typeAlias type="pojo.User" alias="user"/>-->
<package name="pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<!--<mapper resource="mapper/UserMapper.xml" />-->
<package name="mapper"/>
</mappers>
</configuration>
3.5 UserMapperTest
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void creatSqlSessionFactory() throws IOException {
String resource = "SqlMapConfig.xml";
InputStream in = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
}
@Test
public void testFindUserById(){
// 和Spring整合后就省略了
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获得代理对象(到时候就只需要通过Spring容器拿到UserMapper接口的代理对象就可以了)
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User u =userMapper.findUserById(1);
System.out.println(u);
}
@Test
public void testAddUser(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User("小xiao小", "111", "1@com", "123", "sasas");
userMapper.addUser(user);
}
@Test
public void testGetUserByName(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users =userMapper.getUserByName("小小");
for (User u:users)
System.out.println(u);
}
}
4 小结
4.1 selectOne和selectList
动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口方法的返回值决定,如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。
4.2 namespace
mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用pojo包装对象或map对象,保证dao的通用性。
4.3 SqlMapConfig.xml配置文件
SqlMapConfig.xml文件中配置的内容和顺序如下:
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
properties(属性)
如果不按顺序配置,xml会报错
4.3.1 数据库配置
数据配置属性方法一
<properties>
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="jdbc.url" value="jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8"/>
</properties>
数据配置属性方法二
db.driver=com.mysql.jdbc.Driver
db.url = jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8
db.username = root
db.password=root
<properties resource="db.properties"></properties>
<dataSource type="POOLED">
<property name="driver" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</dataSource>
加载properties顺序:
- 先读取在properties元素体内定义的属性
- 然后会读取properties元素中resource或url加载的属性,它会覆盖已读取的同名属性
4.3.2 typeAliases(类型别名)
1. mybatis支持别名
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
map | Map |
2. 自定义别名
2.1配置单个别名
<typeAliases>
<!-- 单个别名定义 别名不区分大小写 -->
<typeAlias type="pojo.User" alias="user"/>
</typeAliases>
2.2配置扫描包的别名
<!-- 配置pojo的别名 -->
<typeAliases>
<!-- 批量别名定义,扫描包的形式创建别名,别名就是类名,且不区分大小写 -->
<package name="pojo"/>
</typeAliases>
4.3.3 SqlMapConfig.xml文件加载mapper.xml文件
<mappers>
<!-- 资源路径加载 -->
<mapper resource="mapper/UserMapper.xml" />
<!-- 类路径加载 -->
<!--注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。-->
<mapper class="mapper/UserMapper.xml"/>
<!--注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。-->
<package name="mapper"/>
</mappers>
在maven项目中,当把UserMapper.xml放在java源文件路径下,而没有放在resources资源路径下,程序可能会抛出Invalid bound statement (not found): mapper.UserMapper.getUserByName
异常
解决方法
在pom.xml中加上如下配置
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>