入门
概念: MyBatis是一个数据持久层(ORM)框架。把实体类和SQL语句之间建
立了映射关系,是一种半自动化的ORM实现
ORM: Object Relationship Mapping 对象关系映射
Mybatis的主要工作是通过sql知晓(获取)查询结果需要转换的Java类以及类中的属性/类型/方法,从而转换为Java对象直接返回给你(ORM原理),以及对数据库的crud(增删改查)操作
主要工作过程:
MyBatis的优点:
- 基于SQL语法,简单易学
- 能了解底层组装过程
- SQL语句封装在配置文件中,便于统一管理与维护,降低了程序的耦合度
- 程序调试方便
MyBatis与Hibernate的区别:
MyBatis的功能架构分为三层:
- API接口层:
提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理 - 数据处理层:
负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作 - 基础支撑层:
负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑
乐观锁和悲观锁
都是用来解决并发问题 面试会问
- 乐观锁(Optimistic Locking): 认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,也就是不采用数据库自身的锁机制,而是通过程序来实现。在程序上,我们可以采用版本号机制或者时间戳机制实现。读取的时候带版本号,写入的时候带着这个版本号,如果不一致就失败,乐观锁适用于多读的应用类型,因为写多的时候会经常失败。在数据库中添加version字段(列)
- 悲观锁(Pessimistic Locking): 也是一种思想,对数据被其他事务的修改持保守态度,会通过数据库自身的锁机制来实现,从而保证数据操作的排它性。就是独占锁,不管读写都上锁了。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。在SQL语句后使用for update进行加锁锁定,更新完之后释放
JDBC
JDBC概念: JDBC(Java Database Connectivity)是Java语言中用于连接和操作数据库的标准API(Application Programming Interface)。JDBC提供了一组类和接口,使得Java应用程序能够与各种关系型数据库进行交互。JDBC允许开发人员使用Java编程语言连接到数据库,执行SQL查询和更新操作,并处理数据库中的数据。它为开发人员提供了一种统一的方式来访问不同数据库的数据,无论底层数据库是Oracle、MySQL、SQL Server还是其他类型的数据库。使用JDBC时,开发人员首先需要加载适当的数据库驱动程序,然后建立与数据库的连接。一旦建立连接,就可以通过JDBC发送SQL语句给数据库,并获取执行结果。JDBC还提供了事务管理、批处理操作和元数据访问等功能,方便对数据库进行管理和操作,JDBC是一个重要的数据库访问技术。
JDBC的过程:
它默认是隐式事务,会自动提交
基础配置及使用
使用Mybatis操作数据库的步骤:
- 导包(支持包、数据库驱动包)
- 创建数据库表对应的实体类
- 写xxxMapper接口及对应的xxxMapper.xml映射文件
- 写主配置文件并在该文件里面加载子配置文件
- 调用ORM接口完成数据库操作
基础配置及讲解示例:
<?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">
<!-- 以上两行是配置MyBatis文件头信息 -->
<configuration>
<environments default="one"> <!-- 默认环境 -->
<environment id="one">
<transactionManager type="JDBC"></transactionManager> <!-- 事务管理者,JDBC默认是隐式事务会自动提交 -->
<dataSource type="POOLED"> <!-- 数据源,这里使用数据库连接池模式,会预先建立所有连接 -->
<property name="driver" value="com.mysql.jdbc.Driver"/> <!-- 加载驱动,高版本的可能是"com.mysql.jdbc.cj.Driver" -->
<property name="url" value="jdbc:mysql://localhost:3306/jkstudy?useUnicode=true&characterEncoding=utf8"/> <!-- 获得数据库的连接 -->
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 加载子配置文件,注意这里是斜杠/不是点. -->
<mapper resource="com/cwl/study/mapper/StudentMapper.xml"/>
</mappers>
</configuration>
//mapper层相当于DAO层
public interface StudentMapper {
public List<Student> selectAll(); //查
public void insertstudent(Student stu); //增
}
StudentMapper.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="com.cwl.study.mapper.StudentMapper"> <!-- 指当前文件为谁而写,这里为StudentMapper写 -->
<select id="selectAll" resultType="com.cwl.study.pojo.Student"> <!-- 如果没有返回值就不写 -->
select sid,sname,ssex from student
</select>
<!-- Mybatis不会自动返回主键值,需要返回时在insert中加属性useGeneratedKey="true" keyProperty="id" -->
<insert id="insertstudent" parameterType="com.cwl.study.pojo.Student"> <!-- 参数返回值类型 -->
insert into student(sname,ssex) values(#{sname},#{ssex}) <!-- “#{}”可以接收参数 -->
</insert>
</mapper>
其中当然还有student实体类(略)
测试类:
public class Test {
public static void main(String[] args) throws Exception {
Reader r=Resources.getResourceAsReader("Mybatis-config.xml"); //读取/加载主配置文件
SqlSessionFactory factory= new SqlSessionFactoryBuilder().build(r); //建立工厂
//打开java程序和数据库的会话,用于发送sql语句(可多条),相当于preparedstatement的作用(只能发送一条sql),SqlSession是Mybatis核心
SqlSession session=factory.openSession();
//通过会话生成对应接口的实现类对象(impl对象)
StudentMapper sm=session.getMapper(StudentMapper.class);
Student stu=new Student();
stu.setSname("哈哈");
stu.setSsex("男");
sm.insertstudent(stu);
session.commit(); //提交事务
}
}
主配置文件的一些主要属性
- properties
- typeAilases(取别名,默认是类名小写)
- settings(全局配置)
- environments(环境)
- mappers(加载子配置文件)
应用:
其中的jdbc.properties文件为:
# 数据库驱动
jdbc.driver=com.mysql.jdbc.Driver
# 地址
jdbc.url=jdbc:mysql://localhost:3306/jkstudy?useUnicode=true&characterEncoding=utf8
# 用户名
jdbc.username=root
# 密码
jdbc.password=123456
子配置文件的一些主要属性
传参问题
StudentMapper类:
public interface StudentMapper {
//java只认类型不认名称,所以有多个不同类型参数需要传给sql时,要为参数取别名
public Student selectByIdAndName(@Param("sid") Integer sid,@Param("name") String name);
}
对应的StudentMapper.xml:
<mapper namespace="com.cwl.study.pojo.StudentMapper">
<!-- 当有多个不同类型参数传过来时,parameterType可直接不写 -->
<select id="selectById" parameterType="java.lang.Integer" resultType="com.cwl.study.pojo.Student">
select sid,sname,ssex from student where sid=#{sid} and name=#{name}
</select>
</mapper>
补充:当数据库列名和类中属性名不一致时,可以在sql中对列加as取别名,后续也可以通过注解来对应
转码问题
有时候只能输出英文而不能输出中文,就在连接数据库url的地方再次转码(转回去),把“amp;”去掉,需要转码的时候加上去就行
# 地址
jdbc.url=jdbc:mysql://localhost:3306/jkstudy?useUnicode=true&characterEncoding=utf8
# 改为:
jdbc.url=jdbc:mysql://localhost:3306/jkstudy?useUnicode=true&characterEncoding=utf8
sql封装和模糊查询
主要是StudentMapper.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="com.cwl.study.mapper.StudentMapper">
<sql id="s1"> <!-- 做代码封装/公共 -->
select sid,sname,ssex from student
</sql>
<!-- 当有多个不同类型参数传过来时,parameterType可直接不写 -->
<select id="selectById" parameterType="java.lang.Integer" resultType="com.cwl.study.pojo.Student">
select sid,sname,ssex from student where sid=#{sid} and name=#{name}
</select>
<select id="selectAllAndOrderBy" parameterType="java.lang.String" resultType="com.cwl.study.pojo.Student">
<include refid="s1"/> <!-- 引入上面公共的部分 -->
order by ${sid} ${str} <!-- 这里用${}来接收参数 -->
</select>
<select id="selectByNameLike" parameterType="java.lang.String" resultType="com.cwl.study.pojo.Student">
<include refid="s1"/>
<!-- sql里面不能用+号做拼接,这里介绍以下三种拼接方法 -->
<!-- where sname like "%"#{name}"%" --> <!-- 通配符写在sql里 -->
<!-- where sname like #{name} --> <!-- 通配符写在被调用方法的参数位置,例如stu.selectByNameLike("%大%"); -->
where sname like concat("%",#{name},"%")<!-- 用slq自带的拼接函数concat(),推荐使用 -->
</select>
</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="com.cwl.study.mapper.GoodsMapper">
<resultMap type="Goods" id="r1"> <!-- 当实体类属性名和数据库的列名不一样时要做映射对应 -->
<id property="id" column="gid" /> <!-- 或者在sql中用别名as指定 -->
<result property="name" column="gname" />
<result property="price" column="gprice" />
<result property="gtype" column="gtype" /> <!-- 以下行是用来一对多的,查两次 -->
<association property="d" javaType="type" column="gtype" select="com.cwl.study.mapper.TyprMapper.selectById"></association>
</resultMap>
<select id="selectAll" resultMap="r1">
select gid,gname,gprice,gtype from goods
</select>
</mapper>
另一种写法:
注意每个属性和列都要做一一对应
<mapper namespace="com.cwl.study.mapper.StudentMapper">
<resultMap id="stuMap" type="student">
<id property="sid" column="sid" />
<result property="name" column="sname" />
<result property="ssex" column="ssex" />
<result property="ssubject" column="ssubject" />
<!--查两次的写法-->
<!--<association property="cla" javaType="classes" column="cid" select="com.cwl.study.mapper.ClassesMapper.selectOneById"></association>
-->
<association property="cla" javaType="classes"> <!--用association绑定一方,需要找的那一方-->
<id property="cid" column="cid" />
<result property="name" column="cname" />
</association>
</resultMap>
<select id="selectById" parameterType="java.lang.Integer" resultMap="stuMap">
select sid,sname,ssex,ssubject,cid from student where sid=#{id}
</select>
再另外一种写法就是用一个中间类,不用resultMap:
以student和classes为例
中间类AllDetail:
public class AllDetail {
private Integer sid;
private String name;
private String ssex;
private String ssubject;
private Integer cid;
private String cname;
所有基础方法省略
}
StudentMapper.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="com.cwl.study.mapper.StudentMapper">
<!--resultMap改成resultType并填中间类名即可-->
<select id="selectById" parameterType="java.lang.Integer" resultType="AllDetail">
select sid,sname as name,ssex,ssubject,cid from student where sid=#{id}
</select>
</mapper>
注意StudentMapper.java里面的方法返回值也要改为中间类AllDetail ,多琢磨一下连表查询
一对多
跟上面结合就形成双向一对多
一方Classes实体类:
//一方
public class Classes {
private Integer cid;
private String name;
private List<Student> stu; //指定绑定的多方,这里可用数组、Map和list,推荐用list
public List<Student> getStu() {
return stu;
}
public void setStu(List<Student> stu) {
this.stu = stu;
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Classes(Integer cid, String name) {
super();
this.cid = cid;
this.name = name;
}
public Classes() {
super();
}
public String toString() { //注意不能重写多方的stu,容易造成无限套娃导致栈溢出
return "Classes [cid=" + cid + ", name=" + name + "]";
}
}
ClassesMapper.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="com.cwl.study.mapper.ClassesMapper">
<resultMap id="classesMap" type="classes">
<id property="cid" column="cid" />
<result property="name" column="cname" />
</resultMap>
<resultMap id="selectStuMap" type="classes">
<id property="cid" column="cid" />
<result property="name" column="cname" />
<collection property="stu" ofType="student"> <!--用collection指定绑定的多方-->
<id property="sid" column="sid" />
<result property="name" column="sname" />
<result property="ssex" column="ssex" />
<result property="ssubject" column="ssubject" />
</collection>
</resultMap>
<select id="selectOneById" parameterType="java.lang.Integer" resultMap="classesMap">
select cid cname from classes where cid=#{cid}
</select>
<select id="selectStuAndClaById" parameterType="java.lang.Integer" resultMap="selectStuMap">
SELECT classes.cid,cname,sid,sname,ssex,ssubject FROM student,classes WHERE student.cid=#{cid} AND classes.cid=#{cid};
</select>
</mapper>
ClassesMapper接口:
public interface ClassesMapper {
//通过id查询班级
public Classes selectOneById(Integer cid);
//通过id查询该班级下的所有学生
public Classes selectStuAndClaById(Integer cid);
}
测试类:
public class Test {
public static void main(String[] args) throws IOException {
Reader r = Resources.getResourceAsReader("MyBatis-config.xml");
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(r);
SqlSession session = ssf.openSession();
//StudentMapper sm=session.getMapper(StudentMapper.class);//先获取StudentMapper,方便调用它的方法
//System.out.println(sm.selectById(6));
//ClassesMapper cm=session.getMapper(ClassesMapper.class);
//System.out.println(cm.selectOneById(6));
ClassesMapper cm=session.getMapper(ClassesMapper.class);
Classes c=cm.selectStuAndClaById(6); //先查出该id对应的班级
System.out.println(c);
for (Student s :c.getStu()) { //再遍历输出该班级下的所有学生
System.out.println(s);
}
}
}
动态SQL
可以使得SQL语句更加灵活
Student实体类略
StudentMapper.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="com.cwl.study.mapper.StudentMapper">
<sql id="s1"> <!--封装/公共-->
select sid,sname,ssex,ssubject,cid from student
</sql>
<select id="selectByIdAndWhere" parameterType="student" resultType="student" >
<include refid="s1"/> <!--引用封装好的SQL-->
<!--where 1=1--> <!--这里的1=1没有意义只是为了满足语法,可以用where代替该行,如下:-->
<where> <!--假设第一个if里面的sid=null,<where>会自动处理后面的and-->
<if test="sid!=null"> <!--动态SQL,可灵活传参,当然也可以写其他的判断条件-->
sid=#{sid}
</if>
<if test="sname!=null">
and sname=#{sname}
</if>
<if test="ssex!=null"> <!--其中的ssex是接收的参数-->
and ssex=#{ssex} <!--而左边的ssex是列名,右边的照样是参数-->
</if>
<if test="ssubject!=null">
and ssubject=#{ssubject}
</if>
<if test="cid!=null">
and cid=#{cid}
</if>
</where>
</select>
<update id="updataById" parameterType="student">
update student
<set> <!--set会自动处理多余的逗号问题-->
<if test="sname!=null"> <!--这样的动态SQL可以使得只修改单个值而不影响其他的值-->
sname=#{sname},
</if>
<if test="ssex!=null">
ssex=#{ssex},
</if>
<if test="ssubject!=null">
ssubject=#{ssubject},
</if>
<if test="cid!=null">
cid=#{cid},
</if>
sid=#{sid} <!--这个要加在这里-->
</set>
where sid=#{sid}
</update>
<insert id="insertStuList" parameterType="student"> <!--使用foreach可以一次性插入多条数据-->
insert into student (sname,ssex,ssubject,cid) values
<foreach collection="list" item="stu" separator=","> <!--collection="list"表示遍历的是list集合-->
(#{stu.sname},#{stu.ssex},#{stu.ssubject},#{stu.cid}) <!--集合里面的每一条数据用item="xxx"指代-->
</foreach> <!--separator=","表示为每一行自动添加逗号-->
</insert>
</mapper>
StudentMapper 接口:
public interface StudentMapper {
public Object selectByIdAndWhere(Student stu);
public void updataById(Student stu);
public void insertStuList(List<Student> stulist);
}
测试类:
public class Test {
public static void main(String[] args) throws IOException {
Reader r=Resources.getResourceAsReader("MyBatis-config.xml");
SqlSessionFactory ssf=new SqlSessionFactoryBuilder().build(r);
SqlSession session=ssf.openSession();
//Student stu=new Student(1,"陳大大","男","信科",1);
/* Student stu=new Student(null,null,"男","信科",null); //可灵活传参
StudentMapper stuMapper=session.getMapper(StudentMapper.class); //获取StudentMapper接口,方便调用它的方法
List<Student> stulist=stuMapper.selectByIdAndWhere(stu);
for (Student s : stulist) {
System.out.println(s);*/
/* Student stu=new Student();
stu.setSid(17);
StudentMapper stuMapper=session.getMapper(StudentMapper.class);
Student stulist=(Student) stuMapper.selectByIdAndWhere(stu); //先查出来
System.out.println(stulist);
stulist.setSname("开发");
stuMapper.updataById(stulist); //再修改
session.commit(); //注意增删改都要提交事务
System.out.println(stulist);*/
StudentMapper stuMapper=session.getMapper(StudentMapper.class);
List<Student> stulist=new ArrayList<>();
stulist.add(new Student(null,"实施","女","外院",4)); //都添加入list集合里
stulist.add(new Student(null,"测试","男","城建",3));
stulist.add(new Student(null,"JAVA","女","信科",4));
stuMapper.insertStuList(stulist); //把集合放进去,即可一次性插入
session.commit();
}
}
注解
Mybatis的注解都是写在Mapper接口的方法上,它的注解主要分为三大类:SQL 语句映射、结果集映射和关系映射
SQL 语句映射
@Insert:实现新增功能
@Insert("insert into user(id,name) values(#{id},#{name})")
public int insert(User user);
@Select:实现查询功能
@Select("Select * from user")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "sex", property = "sex"),
@Result(column = "age", property = "age")
})
List<User> queryAllUser();
@SelectKey:插入后,获取id的值
以 MySQL 为例,MySQL 在插入一条数据后,使用 select last_insert_id() 可以获取到自增 id 的值。
@Insert("insert into user(id,name) values(#{id},#{name})")
@SelectKey(statement = "select last_insert_id()", keyProperty = "id", keyColumn = "id", resultType = int,before = false)
public int insert(User user);
@SelectKey 各个属性含义如下:
- statement:表示要运行的 SQL 语句;
- keyProperty:可选项,表示将查询结果赋值给代码中的哪个对象;
- keyColumn:可选项,表示将查询结果赋值给数据表中的哪一列;
- resultType:指定 SQL 语句的返回值;
- before:默认值为 true,在执行插入语句之前,执行 select last_insert_id()。值为 flase,则在执行插入语句之后,执行 select last_insert_id()。
@Insert:实现插入功能
@Insert("insert into user(name,sex,age) values(#{name},#{sex},#{age}")
int saveUser(User user);
@Update:实现更新功能
@Update("update user set name= #{name},sex = #{sex},age =#{age} where id = #{id}")
void updateUserById(User user);
@Delete:实现删除功能
@Delete("delete from user where id =#{id}")
void deleteById(Integer id);
@Param:映射多个参数,中的 value 属性可省略,用于指定参数的别名。
int saveUser(@Param(value="user") User user,@Param("name") String name,@Param("age") Int age);
结果集映射
@Result、@Results、@ResultMap 是结果集映射的三大注解。
声明结果集映射关系代码:
@Select({"select id, name, class_id from student"})
@Results(id="studentMap", value={
@Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
@Result(column="name", property="name", jdbcType=JdbcType.VARCHAR),
@Result(column="class_id ", property="classId", jdbcType=JdbcType.INTEGER)
})
List<Student> selectAll();
下面为 @Results 各个属性的含义:
- id:表示当前结果集声明的唯一标识;
- value:表示结果集映射关系;
- @Result:代表一个字段的映射关系。其中,column 指定数据库字段的名称,property 指定实体类属性的名称,jdbcType 数据库字段类型,id 为 true 表示主键,默认 false
可使用 @ResultMap 来引用映射结果集,其中 value 可省略:
@Select({"select id, name, class_id from student where id = #{id}"})
@ResultMap(value="studentMap")
Student selectById(Integer id);
关系映射
@one:用于一对一关系映射
@Select("select * from student")
@Results({
@Result(id=true,property="id",column="id"),
@Result(property="name",column="name"),
@Result(property="age",column="age"),
@Result(property="address",column="address_id",one=@One(select="net.biancheng.mapper.AddressMapper.getAddress"))
})
public List<Student> getAllStudents();
@many:用于一对多关系映射
@Select("select * from t_class where id=#{id}")
@Results({
@Result(id=true,column="id",property="id"),
@Result(column="class_name",property="className"),
@Result(property="students", column="id", many=@Many(select="net.biancheng.mapper.StudentMapper.getStudentsByClassId"))
})
public Class getClass(int id);
还可以参考其他的解释说明:Mybatis注解