1、原生JDBC存在的问题
(1)频繁创建连接和关闭连接,浪费资源;
解决办法:用连接池
(2)将sql语句写在了Java代码中,修改sql语句,就要重新编译java文件不利于维护;
解决办法:可以将sql语句写在配置文件中;
(3)设置参数的占位符也是硬编码在java代码中,同样不利于维护;
解决办法:将占位符同sql一起写在配置文件中;
(4)用ResultSet遍历结果集时,需要列名称,被硬编码在java代码中,也不利于维护
解决办法:将查询到的结果直接映射成java对象;
2、mybatis是什么
mybatis是一个持久层框架,是Apache下的顶级项目,它让程序员把主要的精力放在写sql语句上,通过mybatis的映射方式,自由生成(半自动化)满足需求的sql语句;
mybatis可以将PreparedStatement中的输入参数自动进行输入映射,将查询结果灵活映射成java对象
3、mybatis运行原理
(1)全局配置文件(名称,位置不固定)配置了数据源,事务等mybatis运行环境,还要引入映射配置文件,映射配置文件可以有多个,在里面配置sql语句;
(2)根据配置文件,创建会话工厂(SqlSessionFactory),它的作用是创建会话对象;
(3)根据会话工厂创建会话对象(SqlSession),SqlSession是一个接口,面向程序员,用于操作数据库;
(4)Excutor(执行器):是一个接口,SqlSession内部通过执行器来操作数据库;
(5)MappesStatement(底层封装对象):作用是对操作数据库存储封装,包括sql语句,输入参数和输出结果的类型;
4、全局配置文件,名称与位置不固定(一般放在src下,叫SqlMapConfig.xml)
代码:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入dtd约束 -->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置数据库相关信息 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mydb?serverTimezone=GMT%2b8&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 引入映射配置文件 -->
<mapper resource="cn/melo/mapper/UserMapper.xml"/>
</mappers>
</configuration>
5、创建实体类,写映射配置文件和测试
如代码(User.java和UserMapper.xml):
User.java
package cn.melo.bean;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = -6287722201865636064L;
private Integer id;
private String username;
private String password;
public User() {
super();
}
public User(Integer id, String username, String password) {
super();
this.id = id;
this.username = username;
this.password = password;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
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 + "]";
}
}
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="cn.melo.mapper.UserMapper">
<!--namespace是名称空间,目前可以随便写-->
<select id="findById" resultType="cn.melo.bean.User">
<!--id是sql语句的唯一标识,目前可以随便写,resultType是查询后映射成的结果的类型-->
select * from user where id = #{id}
</select>
</mapper>
测试代码
UserTest.java
package cn.melo.test;
import cn.melo.bean.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class UserTest {
@Test
public void test() throws IOException {
//加载配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//得到会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//得到会话
SqlSession session = factory.openSession();
//操作数据库
User user = (User)session.selectOne("cn.melo.mapper.UserMapper.findById",1);
System.out.println(user);
//提交事务
session.commit();
//释放资源
session.close();
}
}
结果:
6、模糊查询(3种方式)
(1)、在映射配置文件中写(不推荐使用):
<select id="findByUsername" resultType="cn.melo.bean.User">
select * from user where username like #{name}
</select>
测试代码:
List<User> list = session.selectList("cn.melo.mapper.UserMapper.findByUsername","%安%");
System.out.println(list);
结果:
(2)用字符串拼接的符号${…}(不推荐使用):
UserMapper.xml中:
<select id="findByUsername" resultType="cn.melo.bean.User">
select * from user where username like '%${value}%'
</select>
测试代码:
List<User> list = session.selectList("cn.melo.mapper.UserMapper.findByUsername","安");
System.out.println(list);
结果和上面是一样的
(3)使用sql的函数进行字符串拼接(推荐使用):
UserMapper.xml中:
<select id="findByUsername" resultType="cn.melo.bean.User">
select * from user where username like concat('%',#{name},'%')
</select>
测试代码和(2)中的一样,结果也是一样的!!!!
7、增删改操作
(1)添加:
UserMapper.xml中:
<insert id="addUser" parameterType="cn.melo.bean.User">
insert into user values(#{id},#{username},#{password});
</insert>
测试代码:
User user = new User(null,"melo","1122");
session.insert("cn.melo.mapper.UserMapper.addUser",user);
结果:
(2)删除:
UserMapper.xml中:
<delete id="deleteUser">
delete from user where id = #{id}
</delete>
测试代码:
//删除
session.delete("cn.melo.mapper.UserMapper.deleteUser",8);
(3)修改:
UserMapper.xml中:
<update id="updateUser" parameterType="cn.melo.bean.User">
update user set username=#{username} where id=#{id}
</update>
测试代码:
//修改
User user1 = new User(1,"Anthony","");
session.update("cn.melo.mapper.UserMapper.updateUser",user1);
结果:
7、添加后返回主键的值
(1)如果是主键自增的情况:先添加,然后再得到id值
UserMapper.xml中:
<insert id="addUser1" parameterType="cn.melo.bean.User">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
select last_insert_id()
</selectKey>
insert into user values(#{id},#{username},#{password})
</insert>
测试代码:
User user = new User(null,"wade","3322");
int rows = session.insert("cn.melo.mapper.UserMapper.addUser1",user);
System.out.println("影响行数:"+rows);
System.out.println("id是"+user.getId());
结果:
(2)如果是uuid的话,就先根据mysql中的函数,得到一个uuid值,然后再赋给bean对象,当然此时bean对象的id是字符串形式的:
UserMapper.xml中:
<insert id="addUser1" parameterType="cn.melo.bean.User">
<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Integer">
select uuid()
</selectKey>
insert into user values(#{id},#{username},#{password})
</insert>
测试代码:
User user = new User(null,"Paker","4411");
int rows = session.insert("cn.melo.mapper.UserMapper.addUser1",user);
System.out.println("影响行数:"+rows);
System.out.println("id是"+user.getId());
注:oracle中就用序列生成主键:sql写:序列化名.nextval()即可!
8、#{}和${}的区别:
(1)#{}表示一个占位符,接收输入参数,类型可以是JavaBean,简单类型,HashMap
(2)${}表示一个拼接符号,会引起sql注入,所以不建议使用,接收的参数也可以是以上三种类型的
mybatis都是通过OGNL来读取对象的属性值参数的!
9、mybatis和Hibernate的区别:
(1)hibernate是一个标准的ORM(Object Relation Mapping)框架,不需要程序员写sql语句,所以很难对sql进行优化;
mybatis是一个不完全的ORM框架,需要程序员自己写sql!
(2)应用场景:
hiberntae适用于需求变化不多的中小型项目,如OA等;
mybatis适用于变化较多的项目,如互联网项目;
10、mybatis开发DAO的思想:
(1)SqlSessionFactoryBuilder:通过它来创建会话工厂对象,每次使用时new出来即可,不用单例
(2)SqlSessionFactory:使用它来创建SqlSession的实例对象,使用单例模式管理;
(3)SqlSession:是面向程序员的接口,用其中的方法来操作数据库,它是线程不安全的,所以它的最佳应用场合是在方法内;
(4)原始的dao开发思路:写dao接口和实现类,向dao实现类中注入SqlSessionFactory,在方法体中通过SqlSessionFactory来创建SqlSession对象;
存在的问题:
A、dao接口实现类方法存在大量模板方法,可以设法提取出来,减轻工作量;
B、调用SqlSession的方法时,映射文件中sql语句的唯一标识的id成了硬编码;
C、调用SqlSession的方法时,传入变量,因为使用泛型,所以即使传入错误的变量类型,在编译阶段也不会报错,很不利于程序员开发!
(5)mapper代理开发dao
A、思路:写mapper接口,相当于dao接口,不用写实现类,但是mapper接口和映射配置文件间要遵循一些开发规范,mybatis自动生成mapper接口的实现类的代理对象;
B、开发规范:
a、在映射配置文件中(以下简称mapper.xml),元素的属性namespace要写mapper接口的全限定类名;
b、mapper接口中的方法名和写sql语句的元素(以下成statement)属性id要一致;
c、mapper接口中方法的参数类型和mapper.xml中statement的parameterType一致;
d、mapper接口中方法的返回值类型和mapper.xml中statement的resultType一致;
当然不要忘了在全局配置文件中引入映射配置文件;
其实,如果mapper接口中的方法返回的是单个bean对象,代理对象内部通过selectOne查询数据库,如果返回的是集合对象:代理对象内部通过selectList查询数据库;
mapper接口中的参数只有一个,可以用包装类型的javabean满足不同的业务方法需求;
11、mybatis的全局(核心)配置文件
配置内容有:
<properties>,<settings>,<typeAliases>,<typeHandlers>,
<objectFactory>,<plugins>,<environments>,
<mappers>都在同级,且都在<configuration>下;
<environments>下有<environment>,
<environment>下有<transaction>和<datasource>;
(1):建议将数据优酷连接参数单独配置在一个db.properties文件中,只需要在核心配置文件中加载db.properties的属性值,就不用再核心配置文件中对数据库连接参数进行硬编码:
如:
db.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?serverTimezone=GMT%2b8&characterEncoding=utf-8
jdbc.username=root
jdbc.password=123
SqlMapConfig.xml(核心配置文件):
<?xml version="1.0" encoding="utf-8"?>
<!-- 引入dtd约束 -->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<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>
<mappers>
<!-- 引入映射配置文件 -->
<mapper resource="cn/melo/mapper/UserMapper.xml"/>
</mappers>
</configuration>
注意:在mybatis中加载属性的顺序:
加载在标签中定义的属性 --> 读取中resource或url中引入的文件的属性,会覆盖同名的已读取属性 --> 读取parameterType传递的属性,也同样会覆盖已读取的同名属性;
所以:建议不要在标签中定义属性,直接写在.properties文件中,且在.properties文件中定义的属性要有一定的特殊性,如xxx.xxx=…
(2)用来配置一些全局参数:如开启二级缓存,延迟加载等!
(3)用来取别名,是针对statement中的parameterType和resultType属性的:
A、mybatis默认支持的别名:基本数据类型在前面加下划线即可,如:byte --> _byte,int --> _int 等;包装数据类型和String,将首字母小写即可;
注意的是:
Integer可以对应两个别名:integer和int
BigDecimal也可以对应两个别名:decimal和bigdecimal
B、自定义别名:
<typeAliases>
<typeAlias type="全限定类名" alias="别名"></typeAlias>
</typeAliases>
最常用的是批量定义别名:
<typeAliases>
<package name="包名"/>
</typeAliases>
mybatis会自动扫描包中的类,然后自动定义别名,别名就是类名(首字母大小写都行)
(4)(类型处理器):mybatis通过类型处理器完成jdbc类型与java类型的转换,通常情况下,mybatis提供的类型处理器已经能满足日常需要,不用自定义!
(5)(引入映射配置文件)
A、通过resources引入单个配置文件:
<mappers>
<!-- 引入映射配置文件 -->
<mapper resource="映射配置文件的路径"/>
</mappers>
B、通过mapper接口加载单个映射配置文件,此时需要遵循一些规范:前提是用mapper接口开发dao的方式,且接口名字与映射文件同名且在一个目录下!
<mappers>
<!-- 引入映射配置文件 -->
<mapper class="接口的全限定类名"></mapper>
</mappers>
C、批量加载映射配置文件:
<package name="包名"/>
mybatis会自动扫描包下的所有映射配置文件进行加载,需要遵循和上面B一样的开发规范!
12、输入映射和输出映射
(1)输入映射:
statement中的parameterType指定输入参数的类型,可以传入简单类型,HashMap和JavaBean。但是有些时候可能需要一些复杂的综合查询,可能需要传入复杂的条件,此时建议用自定义的包装类型的javabean,在包装类中将复杂的包装条件装进去,在parameterType中写包装类的全限定类名即可,在接口中方法的参数是你所包装的类型!
(2)输出映射:statement中resultType进行输出映射,只有查询出的列名和javabean的属性名一致才可以映射成功!如果查出的列名和bean中的属性不一致,查出来就是空的,如果没有一个bean属性与查询出的结果相对应就连bean对象都不会创建!!!
解决办法:定义一个resultMap对列名和bean的属性名之间做一个映射关系,先定义一个resultMap,然后将它作为statement的属性resultMap的值即可!
代码:
<mapper namespace="cn.melo.mapper.UserMapper">
<resultMap id="唯一标识" type="bean的全限定类名">
<!-- 主键用id来配置 -->
<id column="列名" property="bean的属性名"></id>
<!-- 普通列用result来配置 -->
<result column="列名" property="bean的属性名"></result>
</resultMap>
<select id="接口的方法名" resultMap="上面定义的resultMap的唯一标识">
select * from user where id = #{id}
</select>
</mapper>