Mybatis 第一天学习大纲:
MyBatis 本是 apache 的一个开源项目 iBatis,2010年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis。2013年11月迁移到 Github。 MyBatis 是一个优秀的持久层框架,它对 jdbc 的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建 statement、手动设置参数、结果集检索等 jdbc 繁杂的过程代码。
Mybatis 通过 xml 或注解的方式将要执行的各种 statement(statement、preparedStatement、CallableStatement)配置起来,并通过 java 对象和 statement中 的 sql 进行映射生成最终执行的 sql 语句,最后由 Mybatis 框架执行 sql 并将结果映射成 java 对象并返回。
在学习之前我们先了解什么是 pojo、domain、po、vo、bo 及区别。
pojo:不按 mvc 分层,只是 java bean 有一些属性,还有 get、set方法
domain:不按 mvc 分层,只是 java bean 有一些属性,还有 get、set 方法
po:用在持久层,还可以再增加或者修改的时候,从页面直接传入 action 中,它里面的 java bean 类名等于表名,属性名等于表的字段名,还有对应的 get、set 方法
vo:view object 表现层对象,主要用于在高级查询中从页面接收传过来的各种参数,好处是扩展性强
bo:用在 servie 层,现在企业基本不用
相关资料: Java中PO、DO、DTO、 VO、 BO、POJO 、DAO、TO的概念
这些 po、vo、bo、pojo 可以用在各种层面吗?
可以,也就是 po 用在表现层,vo 用在持久层不报错,因为都是普通的 java bean 没有语法错误。但是在企业最好不要混着用,因为这些都是设计的原则,混着用比较乱,不利于代码维护。
先导入创建数据库的 sql 脚本导入到数据库中。
mybatis.sql:
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `orders`
-- ----------------------------
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '下单用户id',
`number` varchar(32) NOT NULL COMMENT '订单号',
`createtime` datetime NOT NULL COMMENT '创建订单时间',
`note` varchar(100) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
KEY `FK_orders_1` (`user_id`),
CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of orders
-- ----------------------------
INSERT INTO `orders` VALUES ('3', '1', '1000010', '2015-02-04 13:22:35', null);
INSERT INTO `orders` VALUES ('4', '1', '1000011', '2015-02-03 13:22:41', null);
INSERT INTO `orders` VALUES ('5', '10', '1000012', '2015-02-12 16:13:23', null);
-- ----------------------------
-- Table structure for `user`
-- ----------------------------
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);
复制代码
表如下:
orders 表数据:
user 表数据:
因为使用的为 MySQL 数据库,使用 JDBC 操作数据库之前得先导入 mysql 的数据库驱动包 mysql-connector-java-5.1.7-bin.jar 。
加载数据库驱动
创建并获取数据库连接
创建 jdbc statement 对象
设置 sql 语句
设置 sql 语句中的参数(使用 preparedStatement)
通过 statement 执行 sql 并获取结果
对 sql 执行结果进行解析处理
释放资源(resultSet、preparedstatement、connection)
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
//定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
//获取预处理statement
preparedStatement = connection.prepareStatement(sql);
//设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "王五");
//向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
复制代码
上边使用 jdbc 的原始方法(未经封装)实现了查询数据库表记录的操作。
5、jdbc 问题总结如下
① 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。 ② Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java 代码。 ③ 使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。 ④ 对结果集解析存在硬编码(查询列名),sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。
1、mybatis配置
SqlMapConfig.xml,此文件作为 mybatis 的全局配置文件,配置了 mybatis 的运行环境等信息。
mapper.xml 文件即 sql 映射文件,文件中配置了操作数据库的 sql 语句。此文件需要在 SqlMapConfig.xml 中加载。
2、通过 mybatis 环境等配置信息构造 SqlSessionFactory 即 会话工厂
4、mybatis 底层自定义了 Executor 执行器接口操作数据库,Executor 接口有两个实现,一个是基本执行器、一个是缓存执行器。
5、Mapped Statement 也是 mybatis 一个底层封装对象,它包装了 mybatis 配置信息及 sql 映射信息等。 mapper.xml 文件中一个 sql 对应一个Mapped Statement对象,sql 的 id 即是 Mapped statement 的 id。
6、Mapped Statement 对 sql 执行输入参数进行定义,包括 HashMap、基本类型、pojo,Executor 通过 MappedStatement 在执行 sql 前将输入的 java 对象映射至 sql 中,输入参数映射就是 jdbc 编程中对 preparedStatement 设置参数。
7、Mapped Statement 对 sq l执行输出结果进行定义,包括 HashMap、基本类型、pojo,Executor 通过 MappedStatement 在执行 sql 后将输出结果映射至 java 对象中,输出结果映射过程相当于 jdbc 编程中对结果的解析处理过程。
补充:
mybaits 的代码由 GitHub 管理,地址: github.com/mybatis/myb…
mybatis-3.4.6.jar----mybatis的核心包 lib----mybatis的依赖包 mybatis-3.4.6.pdf----mybatis使用手册
需求,实现以下功能:
根据用户id查询一个用户信息 根据用户名称模糊查询用户信息列表 添加用户 删除用户 更新用户 复制代码
1、Eclipse 下创建项目
2、引入 jar 包(加入 mybatis 核心包、依赖包、数据驱动包)
3、在 classpath 下创建 log4j.properties ,如下:
# 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 默认使用 log4j 作为输出日志信息。关于 classpath 具体指哪个路径,参考网上资料: java项目里classpath具体指哪儿个路径 。
关于 classpath 路径我的理解:(以项目案例来说)
如下工程目录:
在本地磁盘下目录是这样的:
另外:在工程的 Build Path 中可以看到三个文件夹都有加入到 Build Path 中。
并且可以看到最后三个文件下生成的 class 字节码文件和配置文件都存在如上图设置的 mybatis0523/bin 目录下:
所以 classpath 路径,应该是指 Build Path 下设置的输出文件夹路径,也即加入到 Build Path 的目录。(有时间我再实践验证...)
4、在 classpath 下创建 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.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
</configuration>
复制代码
SqlMapConfig.xml 是 mybatis 核心配置文件,上边文件的配置内容为数据源、事务管理。
5、新建 PO 类,PO 类作为 mybatis 进行 sql 映射使用,PO 类通常与数据库表对应, User.java 如下:
public class User {
private int id;
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
get/set 方法(略......)
复制代码
6、在 classpath 下的 config 目录下创建 sql 映射文件 Users.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="test"> </mapper> 复制代码
namespace :命名空间,用于隔离 sql 语句,后面会讲另一层非常重要的作用。
7、加载映射文件。mybatis 框架需要加载映射文件,将 Users.xml 添加到 SqlMapConfig.xml ,如下:
<mappers>
<mapper resource="User.xml"/>
</mappers>
复制代码
在映射文件 User.xml 中添加:
<!-- 根据id获取用户信息 -->
<select id="findUserById" parameterType="java.lang.Integer" resultType="cn.itheima.pojo.User">
select * from user where id=#{id}
</select>
复制代码
id:sql 语句唯一标识
parameterType:指定传入参数类型,定义输入到 sql 中的映射类型
resultType:定义返回结果映射类型
#{} 占位符:起到占位作用,如果传入的是基本类型(string、long、double、int、boolean、float 等),那么 #{} 中的变量名称可以随意写
测试程序:
@Test
public void testFindUserById() throws Exception{
String resource = "SqlMapConfig.xml";
//通过流将核心配置文件读取进来
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过核心配置文件输入流来创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂创建会话
SqlSession openSession = factory.openSession();
//第一个参数:所调用的sql语句= namespace+.+sql的ID
User user = openSession.selectOne("test.findUserById", 1);
System.out.println(user);
openSession.close();
}
复制代码
运行结果:
在映射文件 User.xml 中添加:
<select id="findUserByUserName" parameterType="java.lang.String" resultType="cn.itheima.pojo.User">
select * from user where username like '%${value}%'
</select>
复制代码
如果返回结果为集合,可以调用 selectList() 方法,这个方法返回的结果就是一个集合,所以映射文件中应该配置成集合泛型的类型。
${} 拼接符:字符串原样拼接,如果传入的参数是基本类型(string、long、double、int、boolean、float 等),那么 ${} 中的变量名称必须是 value。
注意:拼接符有 sql 注入的风险,所以慎重使用。(关于什么是 sql 注入参考网上资料,如:sql注入实例分析)
测试代码中主要的一行: List<User> list = openSession.selectList("test.findUserByUserName", "王");
---------------------------------------------- 小结 ------------------------------------------------------
#{} 表示一个占位符号,通过 #{} 可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换, #{} 可以有效防止 sql 注入。 #{} 可以接收简单类型值或 pojo 属性值。如果 parameterType 传输单个简单类型值, #{} 括号中可以是 value 或其它名称。
${} 表示拼接 sql 串,通过 ${} 可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, ${} 可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值, ${} 括号中只能是value。
parameterType:指定输入参数类型,mybatis 通过 ognl 从输入对象中获取参数值拼接在 sql 中。 resultType:指定输出结果类型,mybatis 将 sql 查询结果的一行记录数据映射为 resultType 指定类型的对象。
selectOne() :查询一条记录,如果使用 selectOne 查询多条记录则抛出异常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70) 复制代码
selectList() :可以查询一条或多条记录。
/////////////////////////////////////////////////////////////////////////////////////////////
在映射文件 User.xml 中添加:
<!-- 未添加自增主键,可以看到最后的运行结果 id 为 0。 -->
<!--<insert id="insertUser" parameterType="cn.itheima.pojo.User" >
insert into user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
-->
<insert id="insertUser" parameterType="cn.itheima.pojo.User" >
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
insert into user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
复制代码
#{} :如果传入的是 pojo 类型,那么 #{} 中的变量名称必须是 pojo 中对应的 属性.属性.属性.....
如果要返回数据库自增主键:可以使用 select LAST_INSERT_ID()
select LAST_INSERT_ID()
如果 mysql 使用 uuid 实现主键,则先把数据库主键 id 类型改为字符串 string 类型。这里需要增加通过 select uuid() 得到 uuid 值
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey resultType="java.lang.String" order="BEFORE"
keyProperty="id">
select uuid()
</selectKey>
insert into user(id,username,birthday,sex,address)
values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>
复制代码
注意:这里使用的 order 是 “BEFORE”,因为先得生成一个 uuid 再把该值放入传入的 User 的 id 属性
部分测试代码:
User user = new User();
user.setUsername("赵四");
user.setBirthday(new Date());
user.setSex("1");
user.setAddress("北京昌平");
System.out.println("====" + user.getId());
openSession.insert("test.insertUser", user);
//提交事务(mybatis会自动开启事务,但是它不知道何时提交,所以需要手动提交事务)
openSession.commit();
复制代码
这里解释一点:为什么测试代码中未看到开启事务却需要提交事务?这是因为 Mybatis 会自动开启事务,但不知道何时提交,所以需要手动提交事务。
在映射文件 User.xml 中添加:
<delete id="delUserById" parameterType="int">
delete from user where id=#{id}
</delete>
复制代码
测试代码中主要的一行: openSession.delete("test.delUserById", 29);
在映射文件 User.xml 中添加:
<update id="updateUserById" parameterType="cn.itheima.pojo.User">
update user set username=#{username} where id=#{id}
</update>
复制代码
部分测试代码:
User user = new User();
user.setId(28);
user.setUsername("王麻子");
openSession.update("test.updateUserById", user);
复制代码
SqlSession 中封装了对数据库的操作,如:查询、插入、更新、删除等。
通过 SqlSessionFactory(会话工厂) 创建 SqlSession(会话),而 SqlSessionFactory 是通过 SqlSessionFactoryBuilder 进行创建。
SqlSessionFactoryBuilder 用于创建 SqlSessionFacoty,SqlSessionFacoty 一旦创建完成就不需要SqlSessionFactoryBuilder 了,因为 SqlSession 是通过 SqlSessionFactory 生产,所以可以将SqlSessionFactoryBuilder 当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。
SqlSessionFactory 是一个接口,接口中定义了 openSession 的不同重载方法,SqlSessionFactory 的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理 SqlSessionFactory。
SqlSession 是一个面向用户的接口, sqlSession 中定义了数据库操作方法。
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
打开一个 SqlSession,使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。如下:
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
复制代码
原始 Dao 开发方法需要程序员编写 Dao 接口和 Dao 实现类。
UserDao.java:
public interface UserDao {
public User findUserById(Integer id);
public List<User> findUserByUserName(String userName);
}
复制代码
UserDaoImpl.java:
public class UserDaoImpl implements UserDao {
private SqlSessionFactory sqlSessionFactory;
//通过构造方法注入
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public User findUserById(Integer id) {
//sqlSesion是线程不安全的,所以它的最佳使用范围在方法体内
SqlSession openSession = sqlSessionFactory.openSession();
User user = openSession.selectOne("test.findUserById", id);
return user;
}
@Override
public List<User> findUserByUserName(String userName) {
SqlSession openSession = sqlSessionFactory.openSession();
List<User> list = openSession.selectList("test.findUserByUserName", userName);
return list;
}
}
复制代码
问题:为什么不能把 SqlSession 也弄成公共的,即定义在方法体外?因为如果弄成公共的,如果来一个 User 对象,同一时间点,有人进行更新操作、有人进行删除操作,那数据就乱了。所以需要放在方法体内,用完了就销毁了,其他人用不了。
测试代码 UserDaoTest.java:
public class UserDaoTest {
private SqlSessionFactory factory;
//作用:在测试方法前执行这个方法
@Before
public void setUp() throws Exception{
String resource = "SqlMapConfig.xml";
//通过流将核心配置文件读取进来
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过核心配置文件输入流来创建会话工厂
factory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() throws Exception{
//将初始化好的工厂注入到实现类中
UserDao userDao = new UserDaoImpl(factory);
User user = userDao.findUserById(1);
System.out.println(user);
}
@Test
public void testFindUserByUserName () throws Exception{
UserDao userDao = new UserDaoImpl(factory);
List<User> list = userDao.findUserByUserName("王");
System.out.println(list);
}
}
复制代码
Mapper 接口开发方法只需要程序员编写 Mapper 接口(相当于 Dao 接口),由 Mybatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边 Dao 接口实现类方法。
Mapper接口开发需要遵循以下规范:
定义 mapper 映射文件 UserMapper.xml(内容同 Users.xml),需要修改 namespace 的值为 UserMapper 接口路径。将 UserMapper.xml 放在classpath 下 mapper目录下。
<?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接口代理实现编写规则:
1. 映射文件中namespace要等于接口的全路径名称
2. 映射文件中sql语句id要等于接口的方法名称
3. 映射文件中传入参数类型要等于接口方法的传入参数类型
4. 映射文件中返回结果集类型要等于接口方法的返回值类型
-->
<mapper namespace="cn.itheima.mapper.UserMapper">
<!--
id:sql语句唯一标识
parameterType:指定传入参数类型
resultType:返回结果集类型
#{}占位符:起到占位作用,如果传入的是基本类型(string,long,double,int,boolean,float等),那么#{}中的变量名称可以随意写.
-->
<select id="findUserById" parameterType="int" resultType="cn.itheima.pojo.User">
select * from user where id=#{id}
</select>
<!--
如果返回结果为集合,可以调用selectList方法,这个方法返回的结果就是一个集合,所以映射文件中应该配置成集合泛型的类型
${}拼接符:字符串原样拼接,如果传入的参数是基本类型(string,long,double,int,boolean,float等),那么${}中的变量名称必须是value
注意:拼接符有sql注入的风险,所以慎重使用
-->
<select id="findUserByUserName" parameterType="string" resultType="user">
select * from user where username like '%${value}%'
</select>
<!--
#{}:如果传入的是pojo类型,那么#{}中的变量名称必须是pojo中对应的属性.属性.属性.....
如果要返回数据库自增主键:可以使用select LAST_INSERT_ID()
-->
<insert id="insertUser" parameterType="cn.itheima.pojo.User" >
<!-- 执行 select LAST_INSERT_ID()数据库函数,返回自增的主键
keyProperty:将返回的主键放入传入参数的Id中保存.
order:当前函数相对于insert语句的执行顺序,在insert前执行是before,在insert后执行是AFTER
resultType:id的类型,也就是keyproperties中属性的类型
-->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
insert into user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
</mapper>
复制代码
3、UserMapper.java(接口文件)
public interface UserMapper {
public User findUserById(Integer id);
//动态代理形势中,如果返回结果集问List,那么mybatis会在生成实现类的使用会自动调用selectList方法
public List<User> findUserByUserName(String userName);
public void insertUser(User user);
}
复制代码
接口定义有如下特点:
修改 SqlMapConfig.xml 文件:
<!-- 加载映射文件 -->
<!-- 使用class属性引入接口的全路径名称:
使用规则:
1. 接口的名称和映射文件名称除扩展名外要完全相同
2. 接口和映射文件要放在同一个目录下
-->
<mappers>
<mapper resource="cn.itheima.mapper.UserMapper"/>
</mappers>
复制代码
补充:如果有很多 mapper 映射文件要加载呢,则每个都写上很麻烦,使用包扫描的方式批量引入Mapper 接口,如下:
<!-- 使用包扫描的方式批量引入Mapper接口
使用规则:
1. 接口的名称和映射文件名称除扩展名外要完全相同
2. 接口和映射文件要放在同一个目录下
-->
<package name="cn.itheima.mapper"/>
复制代码
public class UserMapperTest {
private SqlSessionFactory factory;
//作用:在测试方法前执行这个方法
@Before
public void setUp() throws Exception{
String resource = "SqlMapConfig.xml";
//通过流将核心配置文件读取进来
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过核心配置文件输入流来创建会话工厂
factory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() throws Exception{
SqlSession openSession = factory.openSession();
//通过getMapper方法来实例化接口
UserMapper mapper = openSession.getMapper(UserMapper.class);
User user = mapper.findUserById(1);
System.out.println(user);
}
@Test
public void testFindUserByUserName() throws Exception{
SqlSession openSession = factory.openSession();
//通过getMapper方法来实例化接口
UserMapper mapper = openSession.getMapper(UserMapper.class);
List<User> list = mapper.findUserByUserName("王");
System.out.println(list);
}
@Test
public void testInsertUser() throws Exception{
SqlSession openSession = factory.openSession();
//通过getMapper方法来实例化接口
UserMapper mapper = openSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("老王");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("北京昌平");
mapper.insertUser(user);
openSession.commit();
}
}
复制代码
补充:
☛ selectOne和selectList
动态代理对象调用 sqlSession.selectOne() 和 sqlSession.selectList() 是根据 mapper 接口方法的返回值决定,如果返回 list 则调用 selectList 方法,如果返回单个对象则调用 selectOne 方法。
☛ namespace
mybatis 官方推荐使用 mapper 代理方法开发 mapper 接口,程序员不用编写 mapper 接口实现类,使用 mapper 代理方法时,输入参数可以使用 pojo 包装对象或 map 对象,保证 dao 的通用性。
SqlMapConfig.xml 中配置的内容和顺序如下:
properties(属性) settings(全局配置参数) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境集合属性对象) environment(环境子属性对象) transactionManager(事务管理) dataSource(数据源) mappers(映射器) 复制代码
SqlMapConfig.xml 可以引用 java 属性文件中的配置信息如下:
在 classpath下定义 db.properties 文件:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8 jdbc.username=root jdbc.password=root 复制代码
SqlMapConfig.xml 引用如下:
<properties resource="db.properties"></properties>
<environments default="development">
<environment id="development">
<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>
复制代码
注意,MyBatis 将按照下面的顺序来加载属性
mybatis 支持的别名:
类型别名什么意思呢?在如下映射文件中,parameterType 的值之前写的是 java.lang.Integer :
<select id="findUserById" parameterType="java.lang.Integer" resultType="cn.itheima.pojo.User">
select * from user where id=#{id}
</select>
复制代码
但其实可以写为 Mybatis 支持的别名 int:
<select id="findUserById" parameterType="int" resultType="cn.itheima.pojo.User">
select * from user where id=#{id}
</select>
复制代码
如果碰到 cn.itheima.pojo.User 呢? ---> 可以自定义别名。如下:
在 SqlMapConfig.xml 中配置:
<typeAliases>
<!-- 单个别名定义
type:类的全路劲名称
alias:别名
-->
<typeAlias alias="user" type="cn.itcast.mybatis.po.User"/>
</typeAliases>
复制代码
如果有很多这样类需要别名定义呢?可以使用包扫描方式:
<typeAliases>
<!-- 使用包扫描的方式批量定义别名,如下,会自动扫描cn.itheima.pojo下的所有pojo
定义后别名等于类名,不区分大小写,但是建议按照java命名规则来,首字母小写,以后每个单词的首字母大写(驼峰式命名)
-->
<package name="cn.itheima.pojo"/>
<!--<package name="其它包"/> -->
</typeAliases>
复制代码
注: ①定义后别名等于类名并且不区分大小写 ;②这里所说不区分大小写什么意思呢? 如下例
<select id="findUserById" parameterType="java.lang.Integer" resultType="UserR">
select * from user where id=#{id}
</select>
复制代码
resultType 的值写为 UseR 也没问题的。
Mapper 配置的几种方法:
<mapper resource=" " />
使用相对于类路径的资源
如: <mapperresource="sqlmap/User.xml" />
1.1.1 <mapper class=" " />
使用mapper接口类路径
如: <mapperclass="cn.itcast.mybatis.mapper.UserMapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
<package name=""/>
注册指定包下的所有 mapper 接口
如: <packagename="cn.itcast.mybatis.mapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
<!-- 使用包扫描的方式批量引入Mapper接口
使用规则:
1. 接口的名称和映射文件名称除扩展名外要完全相同
2. 接口和映射文件要放在同一个目录下
-->
<package name="cn.itheima.mapper"/>
复制代码
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接。
2、sql 语句写在代码中造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java 代码。
解决:将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离。
3、向 sql 语句传参数麻烦,因为 sql 语句的 where 条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis 自动将 java 对象映射至 sql 语句,通过 statement 中的 parameterType 定义输入参数的类型。
4、对结果集解析麻烦,sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对象解析比较方便。
解决:Mybatis 自动将 sql 执行结果映射至 java 对象,通过 statement 中的 resultType 定义输出结果的类型。
Mybatis 和 Hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要程序员自己编写 sql 语句,不过mybatis 可以通过 XML 或注解方式灵活配置要运行的 sql 语句,并将 java 对象和 sql 语句映射生成最终执行的 sql,最后将 sql 执行的结果再映射生成 java 对象。
Mybatis 学习门槛低,简单易学,程序员直接编写原生态 sql,可严格控制 sql 执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是 mybatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套 sql 映射文件,工作量大。
Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用 Hibernate 开发可以节省很多代码,提高效率。但是 Hibernate 的学习门槛高,要精通门槛更高,而且怎么设计 O/R 映射,在性能和对象模型之间如何权衡,以及怎样用好 Hibernate 需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
小总结:
1. mybatis是一个持久层框架, 作用是跟数据库交互完成增删改查
2.原生Dao实现(需要接口和实现类)
4.动态代理方式(只需要接口)
mapper接口代理实现编写规则:
1) 映射文件中namespace要等于接口的全路径名称
2) 映射文件中sql语句id要等于接口的方法名称
3) 映射文件中传入参数类型要等于接口方法的传入参数类型
4) 映射文件中返回结果集类型要等于接口方法的返回值类型
5. #{}占位符:占位
如果传入的是基本类型,那么#{}中的变量名称可以随意写
如果传入的参数是pojo类型,那么#{}中的变量名称必须是pojo中的属性.属性.属性...
6. ${}拼接符:字符串原样拼接
如果传入的是基本类型,那么${}中的变量名必须是value
如果传入的参数是pojo类型,那么${}中的变量名称必须是pojo中的属性.属性.属性...
注意:使用拼接符有可能造成sql注入,在页面输入的时候可以加入校验,不可输入sql关键字,不可输入空格
7. 映射文件:
1)传入参数类型通过parameterType属性指定
2)返回结果集类型通过resultType属性指定
8. hibernate和mybatis区别:
hibernate:它是一个标准的orm框架,比较重量级,学习成本高.
优点:高度封装,使用起来不用写sql,开发的时候,会减低开发周期.
缺点:sql语句无法优化
应用场景:oa(办公自动化系统), erp(企业的流程系统)等,还有一些政府项目,
总的来说,在用于量不大,并发量小的时候使用.
mybatis:它不是一个orm框架, 它是对jdbc的轻量级封装, 学习成本低,比较简单
有点:学习成本低, sql语句可以优化, 执行效率高,速度快
缺点:编码量较大,会拖慢开发周期
应用场景: 互联网项目,比如电商,P2p等
总的来说是用户量较大,并发高的项目.
复制代码