MyBatis
JDK - 静态代理
静态代理要求目标和代理对象实现同一个业务接口. 代理对象中的核心功能是由目标来完成的, 代理对象负责增强功能.
什么是静态代理
它是代理模式的一种.
它具备以下特点:
- 目标对象和代理对象实现同一个业务接口
- 目标对象必须实现接口
- 代理对象在程序运行前就已经存在
- 能够灵活的进行目标对象的切换,却无法进行功能的灵活处理(使用动态代理解决此问题)
静态代理的实现
业务功能:请明星进行节目表演.
明星刘德华:目标对象(无法直接访问)
刘德华助理:代理对象(我们可以访问,他还可以跟明星对接)
我们 :客户端对象
代码实现
业务接口 :
public interface Service {
//规定的唱歌的业务功能
void sing();
}
目标对象 :
/**
* 目标对象:刘德华,实现业务接口中的功能,进行唱歌表演
*/
public class SuperStarLiu implements Service {
@Override
public void sing() {
System.out.println("我是刘德华,我正在表演唱歌............");
}
}
代理对象 :
public class Agent implements Service {
//类中的成员变量设计为接口
public Service target; //目标对象
//传入目标对象,方法的参数设计为接口
public Agent(Service target){
this.target = target;
}
@Override
public void sing() {
System.out.println("预订时间..........");
System.out.println("预订场地..........");
//切记切记:业务功能必须由目标对象亲自实现
// SuperStarLiu liu = new SuperStarLiu();
// liu.sing();
//
// SuperStarZhou zhou = new SuperStarZhou();
// zhou.sing();
//面向接口编程:调用时,接口指向实现类
target.sing();
System.out.println("结算费用..........");
}
}
客户端对象 :
@Test
public void testAgent(){
//测试功能
// SuperStarLiu liu = new SuperStarLiu();
// liu.sing();
// Agent agent = new Agent();
// agent.sing();
//有接口和实现类,必须使用接口指向实现类(规范)
Service agent = new Agent();
agent.sing();
}
注意 : 什么是面向接口编程
类中的成员变量设计为接口, 方法的形参设计为接口, 方法的返回值设计为接口, 调用时接口指向实现类.
JDK - 动态代理
代理对象在程序运行的过程中动态在内存构建.可以灵活的进行业务功能的切换.
什么是动态代理
- 目标对象必须实现业务接口
- JDK代理对象不需要实现业务接口
- JDK动态代理的对象在程序运行前不存在.在程序运行时动态的在内存中构建
- JDK动态代理灵活的进行业务功能的切换
- 本类中的方法(非接口中的方法)不能被代理
动态代理用到的类和接口
1. Proxy类
它是java.lang.reflect.Proxy包下的类. 它有一个方法Proxy.newProxyInstance(…..)专门用来生成动态代理对象.
public static Object newProxyInstance(ClassLoader loader, //类加载器
Class<?>[] interfaces,//目标对象实现的所有接口
InvocationHandler h) //它就类似于Agent的功能,代理的功能和目标对象的业务功能调用在这
throws IllegalArgumentException
{...}
2. Method类
反射用的类,用来进行目标对象的方法的反射调用.
method对象接住我们正在调用的方法sing(),show()
method==sing(),show()
method.invoke();==>手工调用目标方法 sing(); show();
3. InvocationHandler接口
它是实现代理和业务功能的.我们在调用时使用匿名内部实现.
接口能够指向所有的实现类.
如果在测试类中把Service agent = (Service)factory.getAgent();
换成ServiceImpl agent = (ServiceImpl)factory.getAgent();
是不行的.
- 因为此时的 ServiceImpl 对象不是 之前的 ServiceImpl 对象, 而是被代理类增强后, 有增强功能的 ServiceImpl 对象, ServiceImpl类无法接住此对象.
- 使用接口可以指向新的实现.
一个简易MyBatis框架
常用的框架SSM
Spring : 它是整合其它框架的框架. 它的核心是IOC和AOP.
SpringMVC : 它是Spring家族的一员, 专门用来优化控制器的.提供了极简单数据提交, 数据携带, 页面跳转等功能.
MyBatis : 是持久化层的一个框架. 用来进行数据库访问的优化. 专注于sql语句. 极大简化了JDBC的访问.
添加框架的步骤 :
新建库建表
新建maven项目
修改目录, 添加缺失的目录
修改pom.xml文件, 添加MyBatis的依赖, 添加MySQL的依赖
修改pom.xml文件, 添加资源文件的指定
在idea中添加数据库的可视化
添加jdbc.properties配置文件(数据库的配置)
jdbc.driverClassName=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true jdbc.username=root jdbc.password=123456
添加SqlMapConfig.xml文件(MyBatis的核心配置文件)
注意文件头
<?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> <!--读取jdbc属性文件--> <!--设置日志输出--> <!--注册实体类别名--> <!--配置环境变量--> <!--注册mapper.xml--> </configuration>
具体实现
<?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> <!--读取属性文件(jdbc.properties) 属性: resource:从resource目录下找指定名称的文件加载(相对路径) url:使用绝对路径加载属性文件 --> <properties resource="jdbc.properties"></properties> <!--配置数据库的环境变量(数据库连接配置) default:使用下面的environment标签的id属性进行指定配置 --> <environments default="development"> <!--在公司开发时用的配置 id:就是提供给environment的default属性使用 --> <environment id="development"> <!--配置事务管理器 - transactionManager type:指定事务管理的方式(追源码可知道有两种选项:jdbc/managed JDBC:事务的控制交给程序员来出来 MANAGED:由容器(spring)来管理事务 --> <transactionManager type="JDBC"></transactionManager> <!--配置数据源 - dataSource type:指定不同的配置方式(追源码可知有三种属性:jndi/pooled/unpooled JNDI:java命名目录接口在服务器(tomcat)端进行数据库连接池的管理 POOLED:使用数据库连接池 UNPOOLED:不使用数据库连接池 --> <dataSource type="POOLED"> <!--配置数据库连接池的基本参数 private String driver; private String url; private String username; private String password; --> <property name="driver" value="${jdbc.driverClassName}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </dataSource> </environment> <!--在家开发时用的配置--> <!-- <environment id="home"> <transactionManager type=""></transactionManager> <dataSource type=""></dataSource> </environment> --> <!--上线时用的配置--> <!-- <environment id="online"> <transactionManager type=""></transactionManager> <dataSource type=""></dataSource> </environment> --> </environments> <!--注册mapper.xml文件 resource:从resource目录下找指定名称的文件进行注册 url:使用绝对路径注册 class:动态代理方式下的注册 --> <mappers> <mapper resource="StudentMapper.xml"></mapper> </mappers> </configuration>
创建实体类Student, 用来封装数据
当前使用的是实体类的成员变量和数据库中的列名相同, 完成映射
在后续也可以使用别名进行成员变量和数据库列名的映射 => 详见 resultMap的简单用法
package com.bjpowernode.pojo; public class Student { private Integer id; private String name; private String email; private Integer age; public Student() { } //做添加的时候, 因为id是自增的, 所以不用给值 public Student(String name, String email, Integer age) { this.name = name; this.email = email; this.age = age; } public Student(Integer id, String name, String email, Integer age) { this.id = id; this.name = name; this.email = email; this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + ", age=" + age + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
添加完成学生表的增删改查的功能的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:是整个文件的大标签,用来开始和结束xml文件 属性: namespace:指定命名空间(相当于包名),用来区分不同mapper.xml文件中相同的id属性 --> <mapper namespace="csd"> <!--查询语句,用到查询时,直接调用id就行--> <!--完成查询全部学生的功能 resultType:指定查询返回的结果集的类型,如果是集合,则必须是泛型的类型 parameterType:如果有参数,则通过它来指定参数的类型 --> <select id="getAll" resultType="com.bjpowernode.beans.Student"> <!--用来写sql语句--> select id,name,email,age from student; </select> </mapper>
创建测试类, 进行功能测试
public class MyTest { @Test public void testA() throws IOException { //使用文件流读取核心配置文件SqlMapConfig.xml InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //创建SqlSessionFactory工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //取出sqlSession对象 SqlSession sqlSession = factory.openSession(); //完成查询操作 List
list = sqlSession.selectList("csd.getAll"); list.forEach(student -> System.out.println(student)); //关闭sqlSession sqlSession.close(); } }
增删改查的功能
查询
StudentMapper.xml中
<!--按主键ID查询-->
<!-- 原先的方法是 :public Student getById(Integer id){}
mybatis中映射关系查表得 int => Integer
-->
<select id="getById" parameterType="int" resultType="com.bjpowernode.beans.Student">
<!--#{}表示占位符,也就是在jdbc中的 ?-->
select id,name,email,age from student where id=#{id};
</select>
<!--模糊查询-->
<!--原先的方法是 List<Student> getByName(String name){}
mybatis中映射关系查表得 string => String
-->
<select id="getByName" parameterType="string" resultType="com.bjpowernode.beans.Student">
<!--${}表示字符串拼接,有两个尖角 '' 只能使用${} -->
select id,name,email,age from student where name like '%${name}%';
</select>
测试类
@Test
public void testGetById() throws IOException {
//使用文件流读取核心配置文件SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//取出sqlSession对象
SqlSession sqlSession = factory.openSession();
//完成查询操作 (按主键ID查询)
Student stu = sqlSession.selectOne("csd.getById", 2);
System.out.println(stu);
//关闭sqlSession
sqlSession.close();
}
@Test
public void testGetByName() throws IOException {
//使用文件流读取核心配置文件SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//取出sqlSession对象
SqlSession sqlSession = factory.openSession();
//完成查询操作 (模糊查询)
List<Student> list = sqlSession.selectList("csd.getByName", "张");
list.forEach(student -> {
System.out.println(student);
});
//关闭sqlSession
sqlSession.close();
}
增加
StudentMapper.xml中
<!--增加学生-->
<!--原先的方法是 int insert(Student stu){}
增删改中, mybatis自动返回, 没有resultType
-->
<select id="insert" parameterType="com.bjpowernode.beans.Student">
insert into student(name,email,age) values(#{name},#{email},#{age});
</select>
测试类
@Test
public void testInsert() throws IOException {
//使用文件流读取核心配置文件SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//取出sqlSession对象
SqlSession sqlSession = factory.openSession();
//增加学生 使用到Student的除主键外的全参构造器
int num = sqlSession.insert("csd.insert", new Student("刘六","123@qq.com",22));
//需要手动提交
sqlSession.commit();
//关闭sqlSession
sqlSession.close();
}
删除
StudentMapper.xml中
<!--按主键删除学生-->
<!--原先的方法是 int delete(Integer id){}-->
<select id="delete" parameterType="int">
delete from student where id = #{id};
</select>
测试类
@Test
public void testDelete() throws IOException {
//使用文件流读取核心配置文件SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//取出sqlSession对象
SqlSession sqlSession = factory.openSession();
//删除学生
int num = sqlSession.delete("csd.delete", 5);
//需要手动提交
sqlSession.commit();
//关闭sqlSession
sqlSession.close();
}
更新
StudentMapper.xml中
<!--按主键更新学生数据-->
<!--原先的方法是 int update(Student stu)-->
<select id="update" parameterType="com.bjpowernode.beans.Student">
update student set name=#{name},email=#{email},age=#{age} where id=#{id};
</select>
测试类
@Test
public void testUpdate() throws IOException {
//使用文件流读取核心配置文件SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//取出sqlSession对象
SqlSession sqlSession = factory.openSession();
//更新学生数据 使用到了Student类的全参构造器
int num = sqlSession.update("csd.update", new Student(6,"刘拉拉","456@qq.com",18));
//需要手动提交
sqlSession.commit();
//关闭sqlSession
sqlSession.close();
}
MyBatis对象分析
Resources类
用来解析SqlMapConfig.xml文件, 创建出相应的对象
SqlSessionFactory接口
SqlSession接口
代码优化
实体类别名注册
在写sql语句时, 有返回值类型需要重复写, 我们这里可以给它在SqlMapConfig.xml文件中起别名, 注意使用标签的顺序.
<!--注册实体类别名,用于重复使用-->
<typeAliases>
<!--单个实体类别名注册-->
<typeAlias type="com.bjpowernode.beans.Student" alias="student"></typeAlias>
<!--批量注册别名
别名是类名的驼峰命名法 Student => student StudentOne => studentOne
-->
<package name="com.bjpowernode.beans"></package>
</typeAliases>
测试类的优化
在写测试类中, 我们发现有重复代码需要不断的实现. 那么我们可以使用@Before与@After注解
public SqlSession sqlSession;
@Before //在所有的Test方法前先执行以下代码
public void openSqlSession() throws IOException {
//使用文件流读取核心配置文件SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//取出sqlSession对象
sqlSession = factory.openSession();
}
@After //在所有Test方法后执行以下代码
public void closeSqlSession(){
//关闭sqlSession
sqlSession.close();
}
设置日志输出
在SqlMapConfig.xml文件设置
<!--设置日志输出底层执行的代码-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
优化mapper.xml注册
在SqlMapConfig.xml文件设置
<!--注册mapper.xml-->
<mappers>
<!--<mapper class="com.bjpowernode.mapper.UserMapper"></mapper>-->
<!--批量注册-->
<package name="com.bjpowernode.mapper"/>
</mappers>
如何访问数据访问层
在三层架构中, 业务逻辑层要通过接口访问数据访问层的功能, 动态代理可以实现
动态代理的实现规范
使用动态代理时, Mapper.xml ( sql语句不能使用select来进行增删改, 必须根据当前sql语句的使用场景使用, 而普通方法可以使用select完成增删改查 )
- UsersMapper.xml文件与UsersMapper.java的接口必须同一个目录下.
- UsersMapper.xml文件与UsersMapper.java的接口的文件名必须一致,后缀不管.
- UserMapper.xml文件中标签的id值与与UserMapper.java的接口中方法的名称完全一致.
- UserMapper.xml文件中标签的parameterType属性值与与UserMapper.java的接口中方法的参数类型完全一致.
- UserMapper.xml文件中标签的resultType值与与UserMapper.java的接口中方法的返回值类型完全一致.
- UserMapper.xml文件中namespace属性必须是接口的完全限定名称com.bjpowernode.mapper.UsersMapper
- 在SqlMapConfig.xml文件中注册mapper文件时,使用class=接口的完全限定名称com.bjpowernode.mapper.UsersMapper.
实现步骤
建表Users
新建maven工程
修改目录
修改pom.xml文件, 添加依赖
添加jdbc.properties文件到resources目录下
添加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> <!--读取jdbc属性文件--> <properties resource="jdbc.properties"></properties> <!--设置日志输出--> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--注册实体类别名--> <typeAliases> <package name="com.bjpowernode.pojo"></package> </typeAliases> <!--配置环境变量--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </dataSource> </environment> </environments> <!--注册mapper.xml--> <mappers> <mapper class="com.bjpowernode.mapper.UserMapper"></mapper> </mappers> </configuration>
添加实体类
package com.bjpowernode.pojo; import java.util.Date; public class Users { private Integer id; private String userName; private Date birthday; private String sex; private String address; public Users() { } @Override public String toString() { return "Users{" + "id=" + id + ", userName='" + userName + '\'' + ", birthday=" + birthday + ", sex='" + sex + '\'' + ", address='" + address + '\'' + '}'; } 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 Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Users(String userName, Date birthday, String sex, String address) { this.userName = userName; this.birthday = birthday; this.sex = sex; this.address = address; } public Users(Integer id, String userName, Date birthday, String sex, String address) { this.id = id; this.userName = userName; this.birthday = birthday; this.sex = sex; this.address = address; } }
添加mapper文件夹, 新建UsersMapper接口
package com.bjpowernode.mapper; import com.bjpowernode.pojo.Users; import java.util.List; /** * 数据访问层的接口,规定的数据库中可以进行的各种操作 */ public interface UserMapper { //查询所有用户 List<Users> getAll(); }
在mapper文件夹下, 新建UsersMapper.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.bjpowernode.mapper.UserMapper"> <!-- //查询所有用户 List<Users> getAll(); --> <select id="getAll" resultType="users"> select id,username,birthday,sex,address from users; </select> </mapper>
添加测试类
@Test public void testGetAll01(){ //取出动态代理的对象,完成接口中方法的调用,实则是调用xml文件中标签的功能 UserMapper userMapper = session.getMapper(UserMapper.class); //xml相当于接口的实现类,getAll被动态代理出来 List<Users> users = userMapper.getAll(); users.forEach(users1 -> { System.out.println(users1); }); } //在@Test前面添加注解@Before和@After
接口类
使用接口类, 让xml实现接口, 从而达到动态代理来访问数据访问层
package com.bjpowernode.mapper;
import com.bjpowernode.pojo.Users;
import java.util.List;
/**
* 数据访问层的接口,规定的数据库中可以进行的各种操作
*/
public interface UserMapper {
//查询所有用户
List<Users> getAll();
//根据用户主键查用户
Users getById(Integer id);
//根据用户名模糊查询
List<Users> getByName(String name);
//用户的更新
int update(Users users);
//增加用户
int insert(Users users);
//根据主键删除用户
int delete(Integer id);
}
查询
<!--根据主键查找用户-->
<select id="getById" resultType="users" parameterType="int">
select id,username,birthday,sex,address from users where id = #{id};
</select>
<!--根据用户名模糊查询-->
<select id="getByName" resultType="users" parameterType="string">
select id,username,birthday,sex,address from users where username like '%${username}%';
</select>
测试类
@Test
public void testGetById01(){
Users users = userMapper.getById(7);
System.out.println(users);
}
@Test
public void testGetByName(){
List<Users> users = userMapper.getByName("张");
users.forEach(users1 -> {
System.out.println(users1);
});
}
//注意使用注解进行重复代码封装
增加
<!--增加用户-->
<insert id="insert" parameterType="users">
insert into users(username, birthday, sex, address) values (#{userName},#{birthday},#{sex},#{address});
</insert>
测试类
//时间刷子
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); //用来完成生日的格式转换
@Test
public void testInsert() throws ParseException {
int num = userMapper.insert(new Users("牛奶", simpleDateFormat.parse("1999-1-1"), "1", "上海"));
System.out.println(num);
session.commit();
}
删除
<!--删除用户-->
<delete id="delete" parameterType="int">
delete from users where id=#{id};
</delete>
测试类
@Test
public void testDelete(){
int num = userMapper.delete(2);
System.out.println(num);
session.commit();
}
更新
<!--用户的更新-->
<update id="update" parameterType="users">
update users set username=#{userName},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id};
</update>
测试类
@Test
public void testUpdate() throws ParseException {
Users u = new Users(2, "豆奶", simpleDateFormat.parse("2000-3-1"),"2","北京" );
int num = userMapper.update(u);
System.out.println(num);
session.commit();
}
#{} 与 ${}
#{} 占位符
传参大部分使用#{}传参,它的底层使用的是PreparedStatement对象,是安全的数据库访问 ,防止sql注入.
#{ } 里面如何写? 看paramenterType参数的类型
如果parameterType的类型是简单类型(8种基本(封装)+String),则#{}里随便写.
<select id="getById" parameterType="int" resultType="users"> ===>入参类型是简单类型 select id,username,birthday,sex,address from users where id=#{zar} ===>随便写 </select>
parameterType的类型是实体类的类型,则#{}里只能是类中成员变量的名称,而且区分大小写.
<insert id="insert" parameterType="users" > ===>入参是实体类 insert into users (username, birthday, sex, address) values(#{userName},#{birthday},#{sex},#{address}) ==>成员变量名称 </insert>
${} 字符串拼接或字符串替换
字符串拼接,一般用于模糊查询中.建议少用,因为有sql注入的风险.
也分两种情况, 同样看parameterType的类型
字符串替换
需求 : 模糊地址和用户查询
select * from users where username like '%小%'; select * from users where address like '%市%'; -- 两个查询语句只有两处不同,可以优化
优化模糊查询
<!--
//优化后的模糊查询
List<Users> getByNameGood(String name);
-->
<select id="getByNameGood" parameterType="string" resultType="users">
select id,username,birthday,sex,address
from users
where username like concat('%',#{name},'%')//把${}换成#{}
</select>
优化多个参数的模糊查询
接口类
//模糊地址和用户查询的方法
List<Users> getByNameOrAddress(
@Param("columnName") ===>为了在sql语句中使用的名称
String columnName,
@Param("columnValue") ===>为了在sql语句中使用的名称
String columnValue);
UsersMapper.xml文件
<!--
//模糊地址和用户查询
//如果参数超过一个,则parameterType不写
List<Users> getByNameOrAddress(
@Param("columnName") ===>为了在sql语句中使用的名称
String columnName,
@Param("columnValue") ===>为了在sql语句中使用的名称
String columnValue);
//columnName列名
-->
<select id="getByNameOrAddress" resultType="users">
select id,username,birthday,sex,address
from users
where ${columnName} like concat('%',#{columnValue},'%') ==> 使用${}来替换列名
</select> ==> 使用的是@Param注解里的名称
测试类
@Test
public void testGetByNameOrAddress(){
List<Users> list = uMapper.getByNameOrAddress("username","小");//按用户名(列名)来模糊查询
list.forEach(users -> System.out.print(users));
}
parameterType总结 :
- 如果参数超过一个,则parameterType不写
- 如果不是实体类和简单类型, 则parameterType不写
resultType总结 :
- 增删改中, mybatis自动返回, 没有resultType
返回主键标签
在完成插入操作后, 将生成的主键信息通过实体类对象返回, 在进行后续关联插入操作时, 不用再次访问数据库
实现步骤
在UsersMapper.xml文件中
<!--增加用户,返回主键-->
<insert id="insert" parameterType="users">
<selectKey keyProperty="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into users(username, birthday, sex, address) values (#{userName},#{birthday},#{sex},#{address});
</insert>
<!--
<selectKey>标签的参数详解:
keyProperty: users对象的哪个属性来接返回的主键值(id)
resultType:返回的主键的类型
order:在插入语句执行前,还是执行后返回主键的值
-->
UUID
这是一个全球唯一随机字符串,由36个字母数字中划线组.
java中
@Test
public void testUUID(){
UUID uuid = UUID.randomUUID();
System.out.println(uuid);
}
MySQL中
select uuid();
动态sql
可以定义代码片段,可以进行逻辑判断,可以进行循环处理(批量处理),使条件判断更为简单.
标签sql与include
: 用来定义代码片段,可以将所有的列名,或复杂的条件定义为代码片段,供使用时调用. : 用来引用 定义的代码片段.
在UsersMapper.xml文件中
<!--定义代码片段-->
<sql id="allColumns">
id,username,birthday,sex,address
</sql>
<!--
//查询所有用户
List<Users> getAll();
-->
<select id="getAll" resultType="users">
select <include refid="allColumns"></include> from users;
</select>
标签if与where
: 进行条件判断 : 进行多条件判断, 在查询, 删除, 更新时使用
实现步骤
接口类
//按指定的条件进行多条件查询
List<Users> getByCondition(Users users);
在UsersMapper.xml文件中
<!--
//按指定的条件进行多条件查询
List<Users> getByCondition(Users users);
根据实体类中的成员变量是否有值来决定是否添加条件
-->
<select id="getByCondition" resultType="users" parameterType="users">
select <include refid="allColumns"></include> from users
<where>
<if test="userName != null and userName != ''">
and username like concat('%',#{userName},'%')
</if>
<if test="birthday != null">
and birthday = #{birthday}
</if>
<if test="sex != null and sex != ''">
and sex = #{sex}
</if>
<if test="address != null and address != ''">
and address like concat('%',#{address},'%')
</if>
</where>
</select>
测试类
@Test
public void getByCondition() throws ParseException {
Users users = new Users();
//给什么赋值就是查什么
users.setSex("1");
users.setBirthday(simpleDateFormat.parse("1999-01-01"));
List<Users> list = userMapper.getByCondition(users);
list.forEach(users1 -> System.out.println(users1));
}
标签set
: 有选择的进行更新处理, 至少更新一列
当我们在进行更新操作的时候, 如果按照以前的方法进行更新, 想要只更新一个名字时, 会发现修改了名字后, 其他没有赋值的列也被更新了(为null)
实现步骤
接口类
//有选择性的更新
int updateBySet(Users users);
在UsersMapper.xml文件中
<!--
//有选择性的更新
int updateBySet(Users users);
-->
<update id="updateBySet" parameterType="users">
update users
<set>
<if test="userName != null and userName != ''">
username = #{userName},
</if>
<if test="birthday != null">
birthday = #{birthday},
</if>
<if test="sex != null and sex != ''">
sex = #{sex},
</if>
<if test="address != null and address !=''">
address = #{address},
</if>
</set>
where id = #{id}; ==>注意不用漏掉
</update>
测试类
@Test
public void updateBySet(){
Users users = new Users();
users.setId(27);
users.setAddress("泉州");
int num = userMapper.updateBySet(users);
System.out.println(num);
session.commit();
}
标签foreach
: 用来进行循环遍历,完成循环条件查询,批量删除(经常用),批量增加(偶尔用),批量更新(很少用).
实现步骤
接口类
//查询多个指定id用户的信息
List<Users> getByIds(Integer arr[]);
在UsersMapper.xml文件中
<!--
//查询多个指定id用户的信息
List<Users> getByIds(Integer arr[]);
当前使用的参数是数组, 所以foreach当中的collecting是arrays
如果是list集合就是list
是map集合就是map
-->
<select id="getByIds" resultType="users">
select <include refid="allColumns"></include>
from users
where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
测试类
@Test
public void getByIds(){
Integer[] arr = {3,4,5};
List<Users> list = userMapper.getByIds(arr);
list.forEach(users -> System.out.println(users));
}
foreach标签参数详解
- collection:用来指定入参的类型,如果是List集合,则为list,如果是Map集合,则为map,如果是数组,则为array.
- item:每次循环遍历出来的值或对象
- separator:多个值或对象或语句之间的分隔符
- open:整个循环外面的前括号
- close:整个循环外面的后括号
批量删除
实现步骤
接口类
//批量删除
int deleteAll(Integer arr[]);
在UsersMapper.xml文件中
<!--
//批量删除
int deleteAll(Integer arr[]);
-->
<delete id="deleteAll">
delete from users where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
测试类
@Test
public void deleteAll(){
Integer[] arr = {27,28,29};
int num = userMapper.deleteAll(arr);
System.out.println(num);
session.commit();
}
批量增加
实现步骤
接口类
//批量增加
int insertAll(List list);
在UsersMapper.xml文件中
<!--
//批量增加
int insertAll(List list);
注意要使用u.userName.... 因为是封装到对象u中去
-->
<insert id="insertAll">
insert into users(username, birthday, sex, address) values
<foreach collection="list" separator="," item="u">
(#{u.userName},#{u.birthday},#{u.sex},#{u.address})
</foreach>
</insert>
测试类
@Test
public void testInsertAll() throws ParseException {
Users users = new Users("a", simpleDateFormat.parse("1999-01-01"), "2", "广东");
Users users1 = new Users("b", simpleDateFormat.parse("1999-01-01"), "2", "广东1");
Users users2 = new Users("c", simpleDateFormat.parse("1999-01-01"), "2", "广东2");
Users users3 = new Users("d", simpleDateFormat.parse("1999-01-01"), "2", "广东3");
List<Users> list = new ArrayList();
list.add(users);
list.add(users1);
list.add(users2);
list.add(users3);
int num = userMapper.insertAll(list);
System.out.println(num);
session.commit();
}
指定参数位置(使用下标)
如果入参是多个,可以通过指定参数位置进行传参. 是实体包含不住的条件.实体类只能封装住成员变量的条件.如果某个成员变量要有区间范围内的判断,或者有两个值进行处理,则实体类包不住.
例如:查询指定日期范围内的用户信息.
如何指定
接口类
//查询指定日期范围内的用户信息
List<Users> getByDate(Date begin, Date end);
在UsersMapper.xml文件中
<!--
//查询指定日期范围内的用户信息
List<Users> getByDate(Date begin, Date end);
有默认的下标, 从0开始, 也就是从arg0开始
-->
<select id="getByDate" resultType="users">
select <include refid="allColumns"></include>
from users
where birthday between #{arg0} and #{arg1};
</select>
测试类
@Test
public void testGetByDate() throws ParseException {
Date begin = simpleDateFormat.parse("1999-01-01");
Date end = simpleDateFormat.parse("1999-12-31");
List<Users> list = userMapper.getByDate(begin, end);
list.forEach(users -> System.out.println(users));
}
指定参数位置(@Param指定参数位置)
- 详见 : 优化多个参数的模糊查询示例
指定参数位置(入参是map)
如果入参超过一个以上, 使用map封装查询条件, 更有语义, 查询条件更明确.
实现步骤
接口类
//入参是map, 查询指定日期范围内的用户信息
List<Users> getByMap(Map map);
在UsersMapper.xml文件中
<!--
//入参是map, 查询指定日期范围内的用户信息
List<Users> getByMap(Map map);
Map里面的成员变量是啥
#{birthdaybegin} 与 #{birthdayend} : 就是map中的key
-->
<select id="getByMap" resultType="users">
select <include refid="allColumns"></include>
from users
where birthday between #{birthdaybegin} and #{birthdayend};
</select>
测试类
@Test
public void testGetByMap() throws ParseException {
Date begin = simpleDateFormat.parse("1999-01-01");
Date end = simpleDateFormat.parse("1999-12-31");
Map map = new HashMap<>();
map.put("birthdaybegin", begin);
map.put("birthdayend", end);
List<Users> list = userMapper.getByMap(map);
list.forEach(users -> System.out.println(users));
}
返回一行Map
如果返回的数据实体类无法包含, 可以使用map返回多张表中的若干数据. 返回后这些数据之间没有任何关系. 就是Object类型. 返回的map的key就是列名或别名.
接口类
//返回一行map 返回姓名和地址封装到map中
Map getReturnMapOne(Integer id);
在UsersMapper.xml文件中
<!--
//返回一行map
Map getReturnMapOne(Integer id);
-->
<select id="getReturnMapOne" resultType="map" parameterType="int">
select username,address from users
where id = #{id};
</select>
测试类
@Test
public void testReturnMapOne(){
Map map = userMapper.getReturnMapOne(1);
System.out.println(map.get("username"));
}
返回多行Map
接口类
//返回多行map
List<Map> getMulMap();
在UsersMapper.xml文件中
<!--
//返回多行map
List<Map> getMulMap();
-->
<select id="getMulMap" resultType="map">
select username,address from users
</select>
测试类
@Test
public void testGetMulMap(){
List<Map> list = userMapper.getMulMap();
list.forEach(map -> System.out.println(map));
}
resultMap的简单用法
在之前我们提到实体类中的成员变量需要和数据库中的列名相同, 以此来完成映射
在这里我们要介绍的是还有两种方法可以完成映射
使用别名进行成员变量和数据库列名的映射
我们可以在写sql语句时, 使用别名(给数据库中的列名起别名)来完成数据库列名与实体类成员变量的名称一致
<select id="getAll" resultType="bookmap"> select bookid id,bookname name from book; </select>
使用resultMap
在之前我们的返回类型都是使用
resultType
, 但是当出现实体类成员变量与数据库列名不一致时, 我们就不能单纯的使用它了, 需要在sql语句起别名, 那么有没有其他的方法呢 ?我们可以使用
resultMap
来自定义举例 : 查询所有图书, 图书的实体类与数据库列名不同
//实体类 private Integer id; private String name; //数据库列名 // bookid 与 bookname
实现步骤
在UsersMapper.xml文件中
<!--使用resultMap完成手工映射--> <resultMap id="bookmap" type="book"> <!--主键绑定 property指的是实体类的成员变量, column指的是数据库中的列名 --> <id property="id" column="bookid"></id> <!--非主键绑定--> <result property="name" column="bookname"></result> </resultMap> <select id="getAll" resultMap="bookmap"> select bookid,bookname from book; </select>
关联关系
一对多关联关系
客户和订单就是典型的一对多关联关系.
一个客户名下可以有多个订单.
客户表是一方,订单表是多方.客户一中持有订单的集合.
使用一对多的关联关系,可以满足查询客户的同时查询该客户名下的所有订单.
实现步骤
1.创建实体类
客户实体类
public class Customer {
private Integer id;
private String name;
private Integer age;
//客户名下的所有订单信息
private List<Orders> ordersList;
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", ordersList=" + ordersList +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<Orders> getOrdersList() {
return ordersList;
}
public void setOrdersList(List<Orders> ordersList) {
this.ordersList = ordersList;
}
public Customer(Integer id, String name, Integer age, List<Orders> ordersList) {
this.id = id;
this.name = name;
this.age = age;
this.ordersList = ordersList;
}
public Customer() {
}
}
订单实体类
package com.bjpowernode.pojo;
public class Orders {
private Integer id;
private String orderNumber;
private Double orderPrice;
@Override
public String toString() {
return "Orders{" +
"id=" + id +
", orderNumber='" + orderNumber + '\'' +
", orderPrice=" + orderPrice +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(String orderNumber) {
this.orderNumber = orderNumber;
}
public Double getOrderPrice() {
return orderPrice;
}
public void setOrderPrice(Double orderPrice) {
this.orderPrice = orderPrice;
}
public Orders(Integer id, String orderNumber, Double orderPrice) {
this.id = id;
this.orderNumber = orderNumber;
this.orderPrice = orderPrice;
}
public Orders() {
}
}
2.创建Mapper文件
我们现在需要由客户来查信息, 所以创建的是CustomerMapper
CustomerMapper接口
/**
* 数据访问层的接口,规定的数据库中可以进行的各种操作
*/
public interface CustomerMapper {
//根据客户ID查询客户所有信息,并且返回客户的所有订单信息
Customer getById(Integer id);
}
CustomerMapper.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.bjpowernode.mapper.CustomerMapper">
<!--
//根据客户ID查询客户所有信息,并且返回客户的所有订单信息
Customer getById(Integer id);
select 字段列表 from 表1 left [outer] join 表2 on 条件...;
ofType指泛型的类型 ==> //客户名下的所有订单信息,一方持有多方的集合
private List<Orders> ordersList;
-->
<resultMap id="customermap" type="customer">
<!--主键绑定,注意数据库中id已经起了别名-->
<id property="id" column="cid"></id>
<!--非主键绑定-->
<result property="name" column="name"></result>
<result property="age" column="age"></result>
<!--ordersList绑定, 返回的订单信息是集合-->
<collection property="ordersList" ofType="orders">
<!--主键绑定,注意数据库中id已经起了别名-->
<id property="id" column="oid"></id>
<!--非主键绑定-->
<result property="orderNumber" column="orderNumber"></result>
<result property="orderPrice" column="orderPrice"></result>
</collection>
</resultMap>
<select id="getById" resultMap="customermap" parameterType="int">
select c.id cid,c.name,c.age,o.id oid,o.orderNumber,o.orderPrice,o.customer_id
from customer c left outer join orders o on c.id = o.customer_id
where c.id = #{id};
</select>
</mapper>
3.测试
测试类
@Test
public void testGetByIdCustomer(){
Customer customer = customerMapper.getById(3);
System.out.println(customer);
}
多对一关联关系
订单和客户就是多对一关联.
站在订单的方向查询订单的同时将客户信息查出.
订单是多方,会持有一方的对象.客户是一方.
实现步骤
1.创建实体类
客户实体类
public class Customer {
private Integer id;
private String name;
private Integer age;
//客户名下的所有订单信息,一方持有多方的集合
private List<Orders> ordersList;
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", ordersList=" + ordersList +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<Orders> getOrdersList() {
return ordersList;
}
public void setOrdersList(List<Orders> ordersList) {
this.ordersList = ordersList;
}
public Customer(Integer id, String name, Integer age, List<Orders> ordersList) {
this.id = id;
this.name = name;
this.age = age;
this.ordersList = ordersList;
}
public Customer() {
}
}
订单实体类
public class Orders {
private Integer id;
private String orderNumber;
private Double orderPrice;
//关联下此订单的客户信息, 多方持有一方的对象
private Customer customer;
@Override
public String toString() {
return "Orders{" +
"id=" + id +
", orderNumber='" + orderNumber + '\'' +
", orderPrice=" + orderPrice +
", customer=" + customer +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(String orderNumber) {
this.orderNumber = orderNumber;
}
public Double getOrderPrice() {
return orderPrice;
}
public void setOrderPrice(Double orderPrice) {
this.orderPrice = orderPrice;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public Orders(Integer id, String orderNumber, Double orderPrice, Customer customer) {
this.id = id;
this.orderNumber = orderNumber;
this.orderPrice = orderPrice;
this.customer = customer;
}
public Orders() {
}
}
2.创建Mapper文件
我们现在需要由订单来查信息, 所以创建的是OredrsMapper
OredrsMapper接口
/**
* 数据访问层的接口,规定的数据库中可以进行的各种操作
*/
public interface OrdersMapper {
//根据订单ID查询订单所以信息, 并返回该订单所属客户的信息
Orders getById(Integer id);
}
OredrsMapper.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.bjpowernode.mapper.OrdersMapper">
<!--
//根据订单ID查询订单所以信息, 并返回该订单所属客户的信息
Orders getById(Integer id);
javaType指的是类型 ==> //关联下此订单的客户信息, 多方持有一方的对象
private Customer customer;
-->
<resultMap id="ordersmap" type="orders">
<!--主键绑定,注意数据库中id已经起了别名-->
<id property="id" column="oid"></id>
<!--非主键绑定-->
<result property="orderNumber" column="orderNumber"></result>
<result property="orderPrice" column="orderPrice"></result>
<!--customer绑定,返回的是客户对象-->
<association property="customer" javaType="customer">
<!--主键绑定,注意数据库中id已经起了别名-->
<id property="id" column="cid"></id>
<!--非主键绑定-->
<result property="name" column="name"></result>
<result property="age" column="age"></result>
</association>
</resultMap>
<select id="getById" parameterType="int" resultMap="ordersmap">
select o.id oid,o.orderNumber,o.orderPrice,o.customer_id,c.id cid,c.name,c.age
from orders o join customer c on c.id = o.customer_id
where o.id = #{id};
</select>
</mapper>
3.测试
测试类
@Test
public void testGetByIdOrders(){
Orders orders = ordersMapper.getById(11);
System.out.println(orders);
}
一对一与多对多关联关系
无论是什么关联关系,如果某方持有另一方的集合,则使用
标签完成映射,如果某方持有另一方的对象,则使用 标签完成映射。
多对多关联
需要使用中间表来进行过渡
多对多关联中,需要通过中间表化解关联关系。中间表描述两张主键表的关联。中间表没有对应的实体类。Mapper.xml文件中也没有中间表的对应标签描述,只是在查询语句中使用中间表来进行关联。
事务
多个操作同时完成,或同时失败称为事务处理.
事务有四个特性:一致性,持久性,原子性,隔离性.
在MyBatis框架中设置事务 : mybatis核心配置文件中
在测试类中, 取出对象的时候, 可以设置成自动提交
sqlSession = factory.openSession();
===>默认是手工提交事务,设置为false也是手工提交事务,如果设置为true,则为自动提交.sqlSession = factory.openSession(true);
===>设置为自动提交,在增删改后不需要commit();
缓存
MyBatis框架提供两级缓存,一级缓存和二级缓存.默认开启一级缓存.
我们有专门的缓存框架redis
缓存就是为了提高查询效率
使用缓存后, 查询的流程 :
查询时先到缓存里查,如果没有则查询数据库,放缓存一份,再返回客户端.下次再查询的时候直接从缓存返回,不再访问数据库.如果数据库中发生commit()操作,则清空缓存.
一级缓存使用的是SqlSession的作用域,同一个sqlSession共享一级缓存的数据.
二级缓存使用的是mapper的作用域,不同的sqlSession只要访问的同一个mapper.xml文件,则共享二级缓存作用域.
ORM
ORM(Object Relational Mapping):对象关系映射
java语言中以对象的方式操作数据,存到数据库中是以表的方式进行存储,对象中的成员变量与表中的列之间的数据互换称为映射.整个这套操作就是ORM.
持久化的操作:将对象保存到关系型数据库中 ,将关系型数据库中的数据读取出来以对象的形式封装