文章目录
一、什么是Mybatis?
** Mybatis原来叫ibatis,时apache的一个开源项目,后来迁移到google code,才交Mybatis.它是一个持久层框架。封装了jdbc对数据库的操作,如不需要手动注解驱动,手动设置参数,建立connection,创建statment,结果集检索等。
Mybatis通过xml或注解把要执行的sql语句配置起来,通过java对象与sql语句映射形成最终的执行语句,最后由mybatis执行sql语句并将结果映射为java对象返回。**
二、jdbc的缺点和mybatis相应的解决办法
- 频繁的创建连接,释放连接等浪费系统资源,影响系统性能。数据库连接池可以解决。
==解法:==在SqlMapConfig.xml文件中配置数据库连接池 - Sql语句存在硬编码,实际应用中sql语句变化较大,需要改动java代码。
解法: 将sql语句配置在XXXmapper.xml文件中,与java代码分离。 - 使用preparedStatement的where条件存在硬编码,因为条件在应用中可能需要变动,需要改sql语句,还要改java代码,很不方便。
解法 Mybatis自动将java对象映射到sql语句,通过parameterType定义参数类型 - 返回结果集存在硬编码,如果查询一列数据,sql变化,列名变化,还是需要改java代码,如果把结果集映射成pojo对象,就比较方便了。
解法: Mybatis自动将sql与句的执行结果映射到java对象,通过resultType定义输出类型。
三、Mybatis架构
- sqlMapConfig.xml是Mybatis的全局配置文件,如果配置数据库连接池(但不一定在这里配),还有就是加载mapper.xml映射文件,其中mapper.xml文件里放了可以映射的sql语句,这些语句有相应的id.
- 通过在java语句中使用sqlMapConfig.xml或在applicationContext.xml文件里配置sqlSessionFactory,用于生产sqlSession.
- sqlSession对数据库进行增删查改等操作。如sqlSession.insert(mapper语句id,对象).
- Executor是mybatis底层自定义的执行器接口,对数据库进行操作,有两种实现,以上基本执行器,另一种是缓存执行器。
- MappedStatment,中文意思是映射语句,Executor通过MappedStatement用传入的java对象把sql语句补充完整,mapper.xml里的语句id也是MappedStatement的id,实际上,一条mapper.xml里的sql语句对应一个MappedStatement,MappedStatement是把语句补充完整用于Executor执行。
- 同时MappedStatement对Executor执行sql语句后返回的结果进行定义,包括pojo,HashMap,原生类对象等,映射到Mapper.xml里resultType里规定的java对象。相当于jdbc里结果集里resultSet.next遍历获取对象。Mybatis对这一步进行了封装。
四、mybatis入门程序
1.创建一个java程序,然后导入mybatis的jar包,还有mybatis里lib下的jar包,再导入jdbc驱动jar包。
2.加入配置文件,sqlMapC,onfig.xml还有log4j.properties
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>
<!-- 和spring整合后 environments配置将废除 -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="bxswls" />
</dataSource>
</environment>
</environments>
</configuration>
Log4j.xml如下
# 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
3.在src下创建pojo包,然后在pojo包下创建User.java类,同时创建数据对应的数据库表
public class User {
private int id;
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
添加get和set方法,重写toString()
...
}
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`birthday` date DEFAULT NULL COMMENT '生日',
`sex` char(1) DEFAULT NULL COMMENT '性别',
`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '王五', null, '2', null);
INSERT INTO `user` VALUES ('10', '张三', '2014-07-10', '1', '北京市');
INSERT INTO `user` VALUES ('16', '张小明', null, '1', '河南郑州');
INSERT INTO `user` VALUES ('22', '陈小明', null, '1', '河南郑州');
INSERT INTO `user` VALUES ('24', '张三丰', null, '1', '河南郑州');
INSERT INTO `user` VALUES ('25', '陈小明', null, '1', '河南郑州');
INSERT INTO `user` VALUES ('26', '王五', null, null, null);
4.在src下建立mapper包,在mapper包里建立User.xml文件,并添加select语句
<?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:命名空间,用于隔离sql,还有一个很重要的作用,后面会讲 -->
<mapper namespace="test">
<!-- id:statement的id 或者叫做sql的id-->
<!-- parameterType:声明输入参数的类型 -->
<!-- resultType:声明输出结果的类型,应该填写pojo的全路径 -->
<!-- #{}:输入参数的占位符,相当于jdbc的? -->
<select id="queryUserById" parameterType="int" resultType="pojo.User">
select * from user where id=#{id}
</select>
</mapper>
在sqlMapConfig.xml添加mapper
<?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配置将废除 -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="bxswls" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/User.xml"/>
</mappers>
</configuration>
5.在src下建立单元测试包unitTest,在测试包加建立测试类
MyTest.class
public class MyTest {
private SqlSessionFactory sqlSessionFactory=null;
@Before
public void init()throws Exception{
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sessionFactoryBuilder=new SqlSessionFactoryBuilder();
// 2. 加载sqlMapConfig.xml配置文件
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 3. 创建SqlSessionFactory对象
this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testQueryUserById()throws Exception{
SqlSession session=sqlSessionFactory.openSession();
User user=session.selectOne("queryUserById",1);
System.out.println(user);
}
}
6.根据用户名进行模糊查询,在User.xml文件里添加查询语句,然后写单元测试
<select id="queryUserByUsername" parameterType="string" resultType="pojo.User">
select * from user where username like #{username}
</select>
@Test
public void testQueryUserByUsername()throws Exception{
SqlSession sqlSession=sqlSessionFactory.openSession();
List<User> userList=sqlSession.selectList("queryUserByUsername","%王%");
for (User user:userList) {
System.out.println(user);
}
}
模糊查询还有第二种方式
<!-- 如果传入的参数是简单数据类型,${}里面必须写value,这里stirng不是简单数据类型,写啥都可以 -->
<select id="queryUserByUsername2" parameterType="string" resultType="pojo.User">
select * from user where username like "%${value}%"
</select>
public void testQueryUserByUsername2()throws Exception{
SqlSession sqlSession=sqlSessionFactory.openSession();
List<User> userList=sqlSession.selectList("queryUserByUsername2","王");
for (User user:userList) {
System.out.println(user);
}
}
**#{}和KaTeX parse error: Expected 'EOF', got '#' at position 12: {}的使用** (1)#̲{}表示占位符,直接#{val…{}表示字符串拼接,上面例子里,用的是 like “% u s e r n a m e 则 能 查 出 姓 张 和 姓 王 的 人 , 导 致 信 息 被 爬 取 , 泄 露 。 因 为 字 符 串 拼 接 不 会 进 行 j d b c 类 型 的 转 换 。 还 有 要 注 意 的 是 简 单 数 据 类 型 , {username}% ”加了双引号,这也是能防止注入的,但如果没加双引号,也没加%%,那么,使用使用字符串拼接sqlSession.selectList("queryUserByUsername2","'%王%' or username like '%张%'"); 则能查出姓张和姓王的人,导致信息被爬取,泄露。因为字符串拼接不会进行jdbc类型的转换。还有要注意的是简单数据类型, username则能查出姓张和姓王的人,导致信息被爬取,泄露。因为字符串拼接不会进行jdbc类型的转换。还有要注意的是简单数据类型,{value},括号内只能是value。
7.实现添加用户,添加用户,占位符要用#,insert后要记得commit
<select id="addUser" parameterType="pojo.User">
INSERT INTO user
(username,birthday,sex,address) VALUES
(#{username},#{birthday},#{sex},#{address})
</select>
@Test
public void testQueryUserByUsername2()throws Exception{
User user = new User();
user.setUsername("张则太");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("蜀国");
SqlSession sqlSession=sqlSessionFactory.openSession();
sqlSession.insert("addUser", user);
sqlSession.commit();
sqlSession.close();
}
8.主键自增,有两种方法
(1)非uuid
查询id的sql
SELECT LAST_INSERT_ID()
通过修改User.xml映射文件,可以将mysql自增主键返回:
如下添加selectKey 标签
<!-- 保存用户 -->
<insert id="saveUser" parameterType="cn.itcast.mybatis.pojo.User">
<!-- selectKey 标签实现主键返回 -->
<!-- keyColumn:主键对应的表中的哪一列 -->
<!-- keyProperty:主键对应的pojo中的哪一个属性 -->
<!-- order:设置在执行insert语句前执行查询id的sql,孩纸在执行insert语句之后执行查询id的sql -->
<!-- resultType:设置返回的id的类型 -->
<selectKey keyColumn="id" keyProperty="id" order="AFTER"
resultType="int">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO `user`
(username,birthday,sex,address) VALUES
(#{username},#{birthday},#{sex},#{address})
</insert>
LAST_INSERT_ID():是mysql的函数,返回auto_increment自增列新记录id值。
效果如下图所示:
返回的id为48,能够正确的返回id了。
(2)uuid实现主键自增
需要增加通过select uuid()得到uuid值
<!-- 保存用户 -->
<insert id="saveUser" parameterType="cn.itcast.mybatis.pojo.User">
<!-- selectKey 标签实现主键返回 -->
<!-- keyColumn:主键对应的表中的哪一列 -->
<!-- keyProperty:主键对应的pojo中的哪一个属性 -->
<!-- order:设置在执行insert语句前执行查询id的sql,孩纸在执行insert语句之后执行查询id的sql -->
<!-- resultType:设置返回的id的类型 -->
<selectKey keyColumn="id" keyProperty="id" order="BEFORE"
resultType="string">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO `user`
(username,birthday,sex,address) VALUES
(#{username},#{birthday},#{sex},#{address})
</insert>
注意这里使用的order是“BEFORE”
五、Dao开发
1.原始Dao开发
(1)原始Dao开发,需要Dao接口,和相应的实现实现类。在src下建立Dao包,再建立UserDao接口,然后写实现类UserDaoImp.class
UserDao
public interface UserDao {
User queryUserById(int id);
List<User> queryUserByUsername(String username);
void savaUser(User user);
}
UserDaoImpl
public class UserDaoImpl implements UserDao {
private SqlSessionFactory sqlSessionFactory;
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
super();
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public User queryUserById(int id) {
//有了工厂,就要创建SqlSession
SqlSession sqlSession=sqlSessionFactory.openSession();
//执行逻辑查询,把原先单元测试的东西放到了这里
User user=sqlSession.selectOne("test.queryUserById", id);
//释放资源
sqlSession.close();
return user;
}
@Override
public List<User> queryUserByUsername(String username) {
//有了工厂,就要创建SqlSession
SqlSession sqlSession=sqlSessionFactory.openSession();
List<User> userlist=sqlSession.selectList("test.queryUserByUsername",username);
sqlSession.close();
return userlist;
}
@Override
public void savaUser(User user) {
//有了工厂,就要创建SqlSession
SqlSession sqlSession=sqlSessionFactory.openSession();
sqlSession.insert("test.addUser", user);
sqlSession.commit();
sqlSession.close();
}
}
(2)在uniTest包下创建UserDaoTest类,同样需要SqlSessionFactoryBuilder,然后加载配置文件。
需要增加通过select uuid()得到uuid值
<!-- 保存用户 -->
<insert id="saveUser" parameterType="cn.itcast.mybatis.pojo.User">
<!-- selectKey 标签实现主键返回 -->
<!-- keyColumn:主键对应的表中的哪一列 -->
<!-- keyProperty:主键对应的pojo中的哪一个属性 -->
<!-- order:设置在执行insert语句前执行查询id的sql,孩纸在执行insert语句之后执行查询id的sql -->
<!-- resultType:设置返回的id的类型 -->
<selectKey keyColumn="id" keyProperty="id" order="BEFORE"
resultType="string">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO `user`
(username,birthday,sex,address) VALUES
(#{username},#{birthday},#{sex},#{address})
</insert>
UserDaoTest
public class UserDaoTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init()throws Exception{
SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
// 2. 加载sqlMapConfig.xml配置文件
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 3. 创建SqlSessionFactory对象
this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testQueryUserById() {
//创建Dao
UserDao userDao=new UserDaoImpl(sqlSessionFactory);
User user=userDao.queryUserById(10);
System.out.println(user);
}
}
对比之前的Mytest.java文件
public class MyTest {
private SqlSessionFactory sqlSessionFactory=null;
@Before
public void init()throws Exception{
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sessionFactoryBuilder=new SqlSessionFactoryBuilder();
// 2. 加载sqlMapConfig.xml配置文件
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 3. 创建SqlSessionFactory对象
this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testQueryUserById()throws Exception{
SqlSession sqlSession=sqlSessionFactory.openSession();
User user=sqlSession.selectOne("queryUserById",1);
sqlSession.close()
System.out.println(user);
}
}
仔细观察,原始Dao开发只是在入门程序的基础上,把MyTest.java里的打开会话sqlSessionFactory.openSession(),sqlSession.selectOne(),还有关闭回话sqlSession.close()都放到了Dao接口实现类DaoImpl里了。使用的时候,只要传入sqlSessionFactory,new 一个DaoImpl实现类即可,
然后用该类或Dao接口调用的查询,添加,修改等方法即可。
2.Mapper动态代理开发
(1)原始Dao开发存在的问题
(1)实现类方法体代码重复,需要openSession,close等,调用sqlSession.selectOne等操作方法,非常不方便。
(2)调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不利于开发维护。
(2)动态代理解决办法
代理要求如下:
(1)Mapper.xml文件的namespace.xml要与mapper接口的类路径相同。
(2)Mapper接口方法名和Mapper.xml中定义的每个statement的id相同。
(3)Mapper接口输入类型Mapper.xml中定义的每个statement的parameterType的类型相同。
(4)Mapper接口方法的返回类型和Mapper.xml中定义的resultType相同。
把原来入门程序的代码copy一份,改名叫mybatis-dao。去掉Dao接口实现类。按照上面要就,该UserMapper.xml文件里的namespace,根据mapper.xml文件里的sql语句名,paramterType,resultType,id写接口。
接口方法要合mapper文件相对应
如何使用,如下,先写单元测试,然后sqlSession.getMapper(接口.class),来获取实现类对象,其实,实现类,我们并没有写,动态代理帮我们写了而已。
public class DongTaiDaoDaiLiTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init()throws Exception{
SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
// 2. 加载sqlMapConfig.xml配置文件
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 3. 创建SqlSessionFactory对象
this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testQueryUserById() {
//创建会话
SqlSession sqlSession=sqlSessionFactory.openSession();
UserDao userDao=sqlSession.getMapper(UserDao.class);
User user=userDao.queryUserById(10);
System.out.println(user);
sqlSession.close();
}
@Test
public void testAddUser() {
User user = new User();
user.setUsername("张则太");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("蜀国");
//创建会话
SqlSession sqlSession=sqlSessionFactory.openSession();
UserDao userDao=sqlSession.getMapper(UserDao.class);
//sqlSession.commit();动态代理帮我们写了
userDao.addUser(user);
sqlSession.close();
}
}
==总结:==到这,动态代理帮我们做了哪些事,现在看来,就帮我们把sqlSession处理数据的一两行代码给解决了,我们不用写sqlSession.insert(),sqlSession.commit()等,并没有什么卵用。连入门程序都不如,还平白多了个接口,气死你。动态代理的关键点是namespace,把接口的类路径放上去即可。
五、sqlMapConfig.xml配置文件
1.配置文件里可以写那些节点。
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
evironment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
2.Properties的使用。
现在在mybatis-dao里改动sqlMapConfig.xml文件,把数据库jdbc的属性独立出来,然后用properties属性来导入相应的值,如下
<?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>
<!-- 是用resource属性加载外部配置文件 -->
<properties resource="db.properties">
<!-- 在properties内部用property定义属性,
其实下面这些属性已经在db.properties里存在了,这里再写,表示可以在这里重新给他们赋值 -->
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="wlsbxs"/>
</properties>
<!-- 和spring整合后 environments配置将废除 -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url"
value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<mapper resource="mapper/User.xml"/>
</mappers>
</configuration>
3.typeAlias类型别名的使用。
在使用pojo对象中,我们长会写包名+类名,如果包名比较长,写起来费劲,如在mapper.xml文件的的resultType=”pojo.User”,本例虽然很短,没有什么,但在一些大项目中长包名是不可避免的,如何解决这个问题呢,可以使用别名。
<typeAliases>
<!-- 可以单个定义别名 -->
<!-- <typeAlias alias="user" type="pojo.User" /> -->
<!-- 批量别名定义,扫描整个包下的类,别名为类名(大小写不敏感) -->
<package name="pojo" />
</typeAliases>
4.Mapper映射器的使用
入门程序里,我们就使用了映射器。<mapper resource=” mapper/User.xml”/>这是我们现在所使用的。
还有几总其它的用法,如下
(1)使用接口类路径
如:
==注意:==此种方法要求mapper接口和mapper映射映射文件名称相同,而且放在同一目录下。
复制一份mybatis-dao项目,命名为mybatis-dao-mapper-classAndPackege,然后把dao包下的UserDao.java改名为UserMapper.java,移动到mapper包下,然后把mapper包下原来的User.xml改名为UserMapper.xml.
(2)注册指定包下的所有mapper接口,实际应用中使用的是这种方法
如:
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
六、Mapper.xml各个参数详解
1.parameterType(输入类型)
前面已经使用过了,可以用来传要输入的对象,但只有一张User表,传入的参数为User对象,但如果有两张表(加一张订单包orders),该怎么做呢?那就得新建一个类,既包含用户User,又包含orders。复制一份mybatis-dao-mapper-classAndPackage包,命名为mybaits-dao-mapper。
pojo下新建一个包装查询类QueryUserWrap,并在UserMapper.xml与UserMapper接口下里添加查询语句与对应的抽象方法。
==注意:==包装类型的使用方法,包装类型里有User对象,我们要的是username,所以要user.username
测试代码如下
结果如下
2.resultType(输出类型)
可以是简单数据类型,也可以是pojo对象,如果查询得到的是多个pojo对象,则自动生成一个pojo对象列表
3.resultMap
resultType可以指定将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名移植才可以映射成功。
(1)新建orders表,创建orders的pojo对象。
Order对象:
public class Order {
// 订单id
private int id;
// 用户id
private Integer userId;
// 订单号
private String number;
// 订单创建时间
private Date createtime;
// 备注
private String note;
get/set。。。
}
(2)mapper包下新建OrdersMapper.xml映射文件,OrdersMapper.java接口文件。
例子:如果sql语句查询得到的字段名与pojo属性名不一致,如查询所有的订单,出现如下情况:
<!-- 查询所有的订单数据 -->
<select id="queryOrderAll" resultType="order">
SELECT id, user_id,
number,
createtime, note FROM `order`
</select>
因为user_id与pojo对象里的userId不一样,所以,映射失败
==该怎么办?==这时候需要用到resultMap,将字段名和pojo属性名做一个映射。如下步骤
(3)在ordersMapper.xml文件下建立查询语句,并添加查询方法,然后用resultMap做映射,最后写单元测试。
OrdersMapper.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:命名空间,用于隔离sql,还有一个很重要的作用,
Mapper动态代理开发的时候使用,需要指定Mapper的类路径 -->
<mapper namespace="mapper.OrdersMapper">
<!-- resultMap最终还是要将结果映射到pojo上,type就是指定映射到哪一个pojo -->
<!-- id:设置ResultMap的id -->
<resultMap type="orders" id="ordersResultMap">
<!-- 定义主键 ,非常重要。如果是多个字段,则定义多个id -->
<!-- property:主键在pojo中的属性名 -->
<!-- column:主键在数据库中的列名 -->
<id property="id" column="id"/>
<!-- 定义普通类型 -->
<result property="userId" column="user_id"/>
<result property="number" column="number" />
<result property="createtime" column="createtime" />
<result property="note" column="note" />
</resultMap>
<!-- 查询所有的订单数据 -->
<select id="queryOrdersAll" resultMap="ordersResultMap">
select id,user_id,number,createtime,note from orders
</select>
</mapper>
OdersMapper.java接口
public interface OrdersMapper {
public List<Orders>queryOrdersAll();
}
单元测试ResultMapTest.java文件
public class resultMapTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init()throws Exception{
SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
// 2. 加载sqlMapConfig.xml配置文件
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 3. 创建SqlSessionFactory对象
this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void queryOrdersAll() {
SqlSession sqlSession=sqlSessionFactory.openSession();
OrdersMapper ordersMapper=sqlSession.getMapper(OrdersMapper.class);
List<Orders> list= ordersMapper.queryOrdersAll();
for(Orders o:list) {
System.out.println(o);
}
}
}
结果如下
4.动态sql
(1)if标签
没有if标签出现的情况
UserMapper.xml配置sql,如下:
<!-- 根据条件查询用户 -->
<select id="queryUserByWhere" parameterType="user" resultType="user">
SELECT id, username, birthday, sex, address FROM `user`
WHERE sex = #{sex} AND username LIKE
'%${username}%'
</select>
编写Mapper接口,如下图:
测试方法如下
public class ifTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init()throws Exception{
SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
// 2. 加载sqlMapConfig.xml配置文件
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 3. 创建SqlSessionFactory对象
this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testQueryUserByWhere() {
SqlSession sqlSession=sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setSex("1");
user.setUsername("张");
List<User> list = userMapper.queryUserByWhere(user);
for(User u:list) {
System.out.println(u);
}
sqlSession.close();
}
}
结果如下
如果注释掉user.setSex(“1”)结果如下:
啥都查不到
该如何解决呢,用if标签,如下
修改UserMapper.xml
<!-- 根据条件查询用户 -->
<select id="queryUserByWhere" parameterType="user" resultType="user">
SELECT id, username, birthday, sex, address FROM `user`
WHERE 1=1
<if test="sex != null and sex != ''">
AND sex = #{sex}
</if>
<if test="username != null and username != ''">
AND username LIKE
'%${username}%'
</if>
</select>
结果如下
(2)where标签
上面的sql语句还有where 1=1这样的语句,很麻烦,可以使用where标签进行改造
改造UserMapper.xml,如下
<!-- 根据条件查询用户 -->
<select id="queryUserByWhere" parameterType="user" resultType="user">
SELECT id, username, birthday, sex, address FROM `user`
<!-- where标签可以自动添加where,同时处理sql语句中第一个and关键字 -->
<where>
<if test="sex != null">
AND sex = #{sex}
</if>
<if test="username != null and username != ''">
AND username LIKE
'%${username}%'
</if>
</where>
</select>
同时注释掉user.setSex和user.setUsername,结果如下
5.sql片段
Sql中可以将sql语句中重复使用的片段提交出来,使用时用include引用即可。
<!-- 根据条件查询用户 -->
<select id="queryUserByWhere" parameterType="user" resultType="user">
<!-- SELECT id, username, birthday, sex, address FROM `user` -->
<!-- 使用include标签加载sql片段;refid是sql片段id -->
SELECT <include refid="userFields" /> FROM `user`
<!-- where标签可以自动添加where关键字,同时处理sql语句中第一个and关键字 -->
<where>
<if test="sex != null">
AND sex = #{sex}
</if>
<if test="username != null and username != ''">
AND username LIKE
'%${username}%'
</if>
</where>
</select>
<!-- 声明sql片段 -->
<sql id="userFields">
id, username, birthday, sex, address
</sql>
如果要使用别的Mapper.xml配置的sql片段,可以在refid前面加上对应的Mapper.xml的namespace
例如下图
5.foreach标签
向sql语句传递数组或list,mybatis使用foreach解析
如,根据多个id查询用户信息
Select*from user where id in (1,10,24)
(1)在原来程序里的pojo里的QueryUserWrapper.java查询用户包装类里添加一个private Listids属性;
(2)在UserMapper.xml添加sql语句如下
<select id="queryUserByIds" parameterType="int" resultType="pojo.User">
select * from user
<where>
<!-- foreach标签,进行遍历 -->
<!-- collection:遍历的集合,这里是QueryUserWrapper的ids属性 -->
<!-- item:遍历的项目,可以随便写,,但是和后面的#{}里面要一致 -->
<!-- open:在前面添加的sql片段 -->
<!-- close:在结尾处添加的sql片段 -->
<!-- separator:指定遍历的元素之间使用的分隔符 -->
<foreach collection="ids" item="item" open="id in (" close=")" separator=",">
#{item}
</foreach>
</where>
</select>
并在接口UserMapper.java里添加相应的方法
然后写单测试,并得出结果,大致如下
结果
还有queryUserByIds(Integer[]array),item里要填array
queryUserByIds(Listlist);item里要填list
如果为String数组的话,可以用java.util.HashMap
Map<String,Object>p=new HashMap<String,Object>;
Str={“aaa”,”bbb”,”ccc”,”ddd”}
p.put(“a”,str);
七、关联查询
1.商品订单数据模型
2.一对一查询
需求:查询所有订单信息,关联查询下单用户信息。也就是调订单信息对应一个客户
Sql语句
SELECT
o.id,
o.user_id userId,
o.number,
o.createtime,
o.note,
u.username,
u.address
FROM
`order` o
LEFT JOIN `user` u ON o.user_id = u.id
(1)方法一:使用resultType
使用resultType,改造订单pojo类,此pojo类中包括了订单信息和用户信息,这样返回对象的时候,mybatis自动把用户信息也注入进来了。
在UserMapper.xml文件里添加如下sql语句
<select id="queryUserOrders" resultType="ordersUser">
select
o.id,
o.user_id
userId,注意,这里必须写userId,这样映射才会正确
o.number,
o.createtime,
o.note,
u.username,
u.address
from
orders o
left join user u on o.user_id=u.id
</select>
结果:
小结:定义专门的pojo作为输出类型,其中定义了sql查询结果集的所有的字段,此方法较为简单,企业中使用普遍。
(2)方法二:使用resultMap
在pojo下的Orders类里加入对应User对象,并重写toString方法,添加get和set方法。
然后在OrderMapper.xml文件里添加sql语句
<resultMap type="orders" id="ordersUserResultMap">
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="number" column="number" />
<result property="createtime" column="createtime" />
<result property="note" column="note" />
<!-- association :配置一对一属性 -->
<!-- property:order里面的User属性名 -->
<!-- javaType:属性类型 -->
<!-- 上面已经配了Orders的属性映射,还需要配Orders里user对象的映射 -->
<association property="user" javaType="user">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
</association>
</resultMap>
<select id="queryOrdersUserResultMap" resultMap="ordersUserResultMap">
select
o.id,
o.user_id,
o.number,
o.createtime,
o.note,
u.username,
u.address
from
orders o
left join user u on o.user_id=u.id
</select>
结果和第一种方法一样。
对比:
第一种方法是新建一个类,用于映射,该类会继承两张表中其中的一张表对应的pojo,然后添加另一张表对应的pojo中的一些属性。
第二种方法是在已有对象表对应的pojo对象上添加另一张表对应的pojo作为属性,然后用resultMap和association进行映射。
3.一对多查询
需求:查询所有用户信息及关联的订单信息。
用户信息与订单信息是一对多的关系
Sql语句
SELECT
u.id,
u.username,
u.birthday,
u.sex,
u.address,
o.id oid,
o.number,
o.createtime,
o.note
FROM
`user` u
LEFT JOIN `order` o ON u.id = o.user_id
(1)在UserMapper.xml里写sql语句,并根据语句id写相应的接口方法。
<!-- 一对多关联,查询订单同时查询该用户下的订单 -->
<select id="queryUserOrdersOneToMany" resultType="OrdersUser">
select
u.id userId,
u.username,
u.birthday,
u.sex,
u.address,
o.id,
o.number,
o.createtime
from
user u
left join orders o on u.id=o.user_id
</select>
(2)重写OrdersUser的toString方法
@Override
public String toString() {
return "Orders [id=" + super.getId() + ", userId=" + super.getUserId() + ", number=" + super.getNumber() + ", createtime=" + super.getCreatetime()
+ ", note=" + super.getNote() + ", username=" + username +", address=" + address + "]";
}
(3)写单元测试和运行结果
八、Mybatis与Spring进行整合
1.整合需要的jar包
(1)spring的jar包
(2)Mybatis的jar包
(3)Spring+Mybatis整合jar包
(4)数据库连接池的jar包
(5)数据库驱动包
2.创建一个java工程,加入配置文件
- 创建完成后导入需要的jar包
- 创建一个config包,mybatis配置SqlMapConfig.xml文件和applicationContext.xml文件放入config
(1)applicationContext.xml文件的配置
先配数据连接池,再配mybatis配置文件,并在配置sqlSessionFactory里放入数据源如下
把Mybaits-dao-mapper文件里的src下的pojo,mapper等文件粘过来
(2)再在sqlMapConfig.xml文件里进行相应的配置,像原先的数据库连接,数据源等都放到了applicatinContext.xml里了,所以,只要配别名和mapper映射器
<?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>
<!-- 设置别名 -->
<typeAliases>
<package name="pojo"/>
</typeAliases>
<mappers>
<package name=”mapper”
</mappers>
</configuration>
3.进行Dao开发,原始Dao开发和动态代理开发
(1)原始Dao开发,把原来原始Dao开发的dao包粘过来,再把user.xml,UserDaoImpl粘到dao包下,并进行相应的修改。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
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-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- 数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="maxActive" value="10"></property>
<property name="maxIdle" value="5"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置mybatis核心配置文件 -->
<property name="configLocation" value="classpath:sqlMapConfig.xml"/>
<!-- 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="userDao" class="dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
</beans>
UserDaoImpl.java
编写DAO实现类,实现类必须集成SqlSessionDaoSupport
SqlSessionDaoSupport提供getSqlSession()方法来获取SqlSession
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
@Override
public User queryUserById(int id) {
// 获取SqlSession
SqlSession sqlSession = super.getSqlSession();
// 使用SqlSession执行操作
User user = sqlSession.selectOne("queryUserById", id);
// 不要关闭sqlSession
return user;
}
@Override
public List<User> queryUserByUsername(String username) {
// 获取SqlSession
SqlSession sqlSession = super.getSqlSession();
// 使用SqlSession执行操作
List<User> list = sqlSession.selectList("queryUserByUsername", username);
// 不要关闭sqlSession
return list;
}
@Override
public void saveUser(User user) {
// 获取SqlSession
SqlSession sqlSession = super.getSqlSession();
// 使用SqlSession执行操作
sqlSession.insert("saveUser", user);
// 不用提交,事务由spring进行管理
// 不要关闭sqlSession
}
}
配置dao
测试
创建测试方法,可以直接创建测试Junit用例。
如下图所示进行创建。
编写测试方法如下:
public class UserDaoTest {
private ApplicationContext context;
@Before
public void setUp() throws Exception {
this.context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
@Test
public void testQueryUserById() {
// 获取userDao
UserDao userDao = this.context.getBean(UserDao.class);
User user = userDao.queryUserById(1);
System.out.println(user);
}
@Test
public void testQueryUserByUsername() {
// 获取userDao
UserDao userDao = this.context.getBean(UserDao.class);
List<User> list = userDao.queryUserByUsername("张");
for (User user : list) {
System.out.println(user);
}
}
@Test
public void testSaveUser() {
// 获取userDao
UserDao userDao = this.context.getBean(UserDao.class);
User user = new User();
user.setUsername("曹操");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("三国");
userDao.saveUser(user);
System.out.println(user);
}
}
(2)动态代理开发
方法1:现在有了spring,可以把接口,sqlSessionFactory配置到bean里
<!-- Mapper代理的方式开发方式一,配置Mapper代理对象 -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- 配置Mapper接口 -->
<property name="mapperInterface" value="mapper.UserMapper"/>
<!-- 配置sqlSessionFactory -->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
然后写测试单元
public class DynamicMapperTest {
private ApplicationContext context;
@Before
public void setUp() throws Exception {
this.context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
@Test
public void testQueryUserById() {
UserMapper userMapper=context.getBean(UserMapper.class);
User user = userMapper.queryUserById(10);
System.out.println(user);
}
}
方法2:上面方法只能配置一个UserMapper接口,效率不高,如何配置多个接口呢,可以用包扫描方式
<!-- Mapper代理的方式开发方式二,扫描包方式配置代理 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 配置Mapper接口 -->
<property name="basePackage" value="mapper"/>
</bean>
九、逆向工程
使用官方网站的Mapper自动生成工具mybatis-generator-core-1.3.2来生成po类和Mapper映射文件
【使用插件】
mybatis-generator-core :进入https://mvnrepository.com/搜索 MyBatis ,找到 MyBatis Generator Core
1.导入逆向工程
使用课前资料已有逆向工程,如下图:
2.复制逆向工程到工作空间中
复制的效果如下图:
3.导入逆向工程到eclipse中
如下图方式进行导入:
4.修改配置文件
在generatorConfig.xml中配置Mapper生成的详细信息,如下图:
注意修改以下几点:
1.修改要生成的数据库表
2.pojo文件所在包路径
3.Mapper所在的包路径
配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="root">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver" connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg" password="yycg"> </jdbcConnection> -->
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL
和 NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="cn.itcast.ssm.po"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="cn.itcast.ssm.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cn.itcast.ssm.mapper" targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表 -->
<table schema="" tableName="user"></table>
<table schema="" tableName="order"></table>
</context>
</generatorConfiguration>
5.生成逆向工程代码
找到下图所示的java文件,执行工程main主函数,
刷新工程,发现代码生成,如下图:
6.测试逆向工程代码
(1) 复制生成的代码到mybatis-spring工程,如下图
(2)修改spring配置文件
在applicationContext.xml修改
<!-- Mapper代理的方式开发,扫描包方式配置代理 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 配置Mapper接口,如果需要加载多个包,直接写进来,中间用,分隔 -->
<!-- <property name="basePackage" value="cn.itcast.mybatis.mapper" /> -->
<property name="basePackage" value="cn.itcast.ssm.mapper" />
</bean>
(3) 编写测试方法:
public class UserMapperTest {
private ApplicationContext context;
@Before
public void setUp() throws Exception {
this.context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
@Test
public void testInsert() {
// 获取Mapper
UserMapper userMapper = this.context.getBean(UserMapper.class);
User user = new User();
user.setUsername("曹操");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("三国");
userMapper.insert(user);
}
@Test
public void testSelectByExample() {
// 获取Mapper
UserMapper userMapper = this.context.getBean(UserMapper.class);
// 创建User对象扩展类,用户设置查询条件
UserExample example = new UserExample();
example.createCriteria().andUsernameLike("%张%");
// 查询数据
List<User> list = userMapper.selectByExample(example);
System.out.println(list.size());
}
@Test
public void testSelectByPrimaryKey() {
// 获取Mapper
UserMapper userMapper = this.context.getBean(UserMapper.class);
User user = userMapper.selectByPrimaryKey(1);
System.out.println(user);
}
}
注意:
1.逆向工程生成的代码只能做单表查询
2.不能在生成的代码上进行扩展,因为如果数据库变更,需要重新使用逆向工程生成代码,原来编写的代码就被覆盖了。
3.一张表会生成4个文件
十、缓存
1.一级缓存
MyBatis是默认开启一级缓存,一级缓存是指session缓存,它的作用域是一个sqlSession对应着一个缓存空间,互不影响。
当你再同一个sqlSession执行sql时,第一次会查询数据库,写入到缓存中,第二次会先去缓存中获取,没有命中就再次查询数据库。
当执行增删改的操作时,MyBatis会把SqlSession对应的缓存清空。
Spring项目中一个Mapper文件对应着一个SqlSession。
2.二级缓存
二级缓存是需要手动开启,在配置文件里加
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true" />
</settings>
二级缓存是在一级缓存的基础上开启多个SqlSession对应缓存空间共享
这样我就可以在第一个SqlSession执行一条查询语句时会去数据库中读取并存入对应的缓存空间,第二个SqlSession执行同一条查询语句时会先去所有的SqlSession对应的缓存空间中获取数据。
值得一提的是 当一个SqlSession执行增删改的时候,他只会清空自己对应的缓存空间,假如第一个缓存中存储的name叫"小明"
第二个SqlSession执行修改操作改为name"小红",然后执行查询语句会命中第一个缓存中的"小明" 这就会造成脏读。
解决办法
如果是两个mapper命名空间的话,可以使用 来把一个命名空间指向另外一个命名空间,从而消除上述的影响,再次执行,就可以查询到正确的数据。