知世故而不世故 是善良的成熟
文章目录
1、Mybatis介绍
MyBatis(原名为iBatis)是一种开源的持久层框架,它简化了在Java应用程序中的数据库访问。MyBatis通过将数据库查询和映射任务从Java代码中分离出来,提供了一种灵活且强大的方式来处理数据持久化。
1.1 JDBC痛点
JDBC是Java中对数据库进行读写的标准,原生JDBC的痛点在于:
- SQL夹在Java代码中,耦合度高导致每次修改SQL都需要重新测试,编译,打包,部署,维护不易。
- 大量冗余的模板代码,编写起来费力。
1.2 程序员的诉求
对于开发人员来说,我们希望掌握核心科技,即SQL是由我们编写的,而其他脏活和累活由框架为我们自动完成。
除此之外,我们还希望SQL和java代码分离,保持低耦合。这样在频繁需要修改SQL的场景下,只需要修改SQL语句的编码文件,而无需更改java源代码,简化部署流程。
1.3 Mybatis简介
MyBatis是一个java编写的轻量级(使用方式简单)的半自动(sql自己写,其他框架做)的ORM映射的Dao层框架,
Dao(DataBase Access Object):指java程序中专门用于访问数据库的对象:
ORM(Object Relation Mapping):指将java程序中封装数据的Bean和数据库中的保存数据的表结构进行映射。
ORM的规则中:
- 一种Bean对应一张表
- Bean的一个属性对应表中的一列
- 一个Bean对应数据库中的一行
- 读数据库即是把表中的行封装为一个Bean
- 写数据库就是把Bean中的属性写入到表中
2、数据准备
以查询数据库中指定id的员工为例
2.1 数据准备
CREATE DATABASE `Mybatis` CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; CREATE TABLE employee( id INT(11) PRIMARY KEY AUTO_INCREMENT, last_name VARCHAR(255), gender VARCHAR(10), email VARCHAR(255) ); INSERT INTO employee(last_name,gender,email) VALUES('Tom','male','Tom@163.com'); INSERT INTO employee(last_name,gender,email) VALUES('Jack','male','Jack@163.com'); INSERT INTO employee(last_name,gender,email) VALUES('Marry','female','Tom@163.com');
2.2 建工程
创建IDEAproject,在相关的module的pom中添加如下依赖:
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!-- mysql数据驱动包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> <!-- bean --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> <scope>provided</scope> </dependency> <!-- 测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- 打印日志 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
2.3 Employee类
@Data @AllArgsConstructor @NoArgsConstructor public class Employee { private Integer id; private String lastName; private String gender; private String email; }
2.4 Mybatis的全局配置
在resources目录下加入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> <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://hadoop102:3306/Mybatis?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="000000"/> </dataSource> </environment> </environments> </configuration>
2.5 编写要执行的SQL
myba的优点在于java代码和sql语句分离。sql语句编写在xml文件中,同时注意需要再全局配置文件中注册
在resources中添加存放sql语句的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="good"> <select id="a" resultType="com.zhm.mybatis.beans.Employee"> select * from employee where id = #{id} </select> </mapper>
之后需要再全局的配置中注册,即让MyBatis可以通过指定的xml文件找到辨析的sql。在mybatis.xml的最后位置添加如下内容
<mappers> <mapper resource="sql.xml"/> </mappers> • 1 • 2 • 3
如果希望在控制台看到发送的sql语句,可以在resources下添加log4j.xml文件。内容如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> <param name="Encoding" value="UTF-8" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" /> </layout> </appender> <logger name="java.sql"> <level value="debug" /> </logger> <logger name="org.apache.ibatis"> <level value="info" /> </logger> <root> <level value="debug" /> <appender-ref ref="STDOUT" /> </root> </log4j:configuration>
2.6 编写java程序
public class MybatisDemo1 { public static void main(String[] args) throws IOException { //读取配置文件 String resource = "mybatis.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); /* 发送sql 查询某个id的员工 selectOne(String statement, Object parameter) statement: sql语句。使用 namespace.id的方式来引用 parameter: 语句中传入的参数 */ /* Object o = sqlSession.selectOne("good.a", inputStream); Employee employee = (Employee) o;*/ // 泛型方法的调用: 对象.<泛型类型>方法() Employee employee = sqlSession.<Employee>selectOne("good.a", 1); System.out.println(employee); sqlSession.close(); } }
运行截图:
2.7 稍微总结一下流程
1、首先是要在mysql中有表
2、根据表的信息来创建对应的Bean
3、然后如果没有MyBatis的全局配置就配置一个
4、在resources中编写一个要执行的sql配置文件
5、编写java程序测试能不能从数据库中将数据封装为Bean
3、解决属性无法封装的问题
眼神好的小伙伴应该已经看出,我上一步执行的程序是有问题的,运行结果中的对象的lastName的值是null,但是在数据库中却不是null。
这就是要涉及java的规范和mysql库的规范扯上关系了,java的属性一般是驼峰式命名的,但是mysql却是下划线作为分隔符的,这就会导致java在生成某个属性方法式也是利用驼峰式生成的方法(举个例子吧,比如Employee中的lastname想要和数据库中的last_name映射上就得设置映射规则)
在mybatis.xml中添加如下配置:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> • 1 • 2 • 3
注意:这个配置是有顺序的比较前
解释:配置了这个MyBatis在映射的时候就会把下划线转为驼峰式的映射规则
运行截图:
4、接口式编程
使用MyBatis原生API进行开发有以下不便之处:
我们更倾向于使用Dao—DaoImpl来分层解耦
方法的返回值是Object类型,不能直接使用,需要强转
方法的入参无法进行严格的检查
在此,推荐使用接口式编程开发。即在接口中明确Dao层对象方法的参数类型和返回值类型,通过接口中的Dao层方法,调用SQL。
这对编写SQL的xml文件提出以下硬性要求:
namespace:必须和接口的全类名一致
sql的id:必须和对应的方法名一致
select标签中,返回值类型和参数类型也必须和对应的方法一致
4.1 编写Dao层接口
Dao层接口在myba中相关称之为Mapper。
public interface EmployeeMapper { //根据id查询员工 Employee getEmpById(Integer id); //根据id删除员工信息 void deleteEmpById(Integer id); //新增员工信息 void insertEmp(Employee employee); //修改员工信息 void updateEmp(Employee employee); //查询所有 List<Employee> getAll();
4.2 编写Mapper所使用的SQL(EmployeeMapper.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: 名称空间,类似包名。 必须和对应的接口的全类名一致 --> <mapper namespace="com.atguigu.mybatis.mapper.EmployeeMapper"> <!-- 定义sql,是什么语句,就用什么标签。 select语句,使用 <select>标签 语句标签上的id,是这条语句在这个文件中的唯一标识,必须保证唯一。 要和对应的方法名一致! 如果是查询语句,必须在标签头上声明返回值类型 resultType: 查询的结果集中的一行要封装为的Bean的类型 --> <!-- 字面量(字面上就能看出变量值的变量): 基本数据类型及包装类,String int a = 1; double b = 2.0d; String s = "haha"; 非字面量: Employee e = new Employee(); 关于占位符: 在jdbc中,使用?作为占位符 在mybatis中,使用 #{xxx}作为占位符 xxx: 如果传参的方法中,传入的参数中只有一个且是普通参数(字面量),xxx可以随便写. 如果传参的方法中,传入的参数中只有一个且是Bean,xxx可以写Bean的属性名,#{xxx}就能获取Bean的属性值。 --> <select id="getEmpById" resultType="com.atguigu.mybatis.beans.Employee"> <include refid="sql1"/> where id = #{id} </select> <!-- void updateEmp(new Employee(1, "jack", "a", "b")); #{id} = 1 #{lastName} = jack --> <update id="updateEmp"> update employee set last_name = #{lastName} , gender = #{gender} ,email = #{email} where id = #{id} </update> <insert id="insertEmp"> insert into employee(last_name,gender,email) values(#{lastName},#{gender},#{email}) </insert> <delete id="deleteEmpById"> delete from employee where id = #{xxxx} </delete> <select id="getAllEmp" resultMap="rm1"> select * from employee </select> </mapper>
编写完之后要在全局的配置文件中注册
<mappers> <mapper resource="EmployeeMapper.xml"/> </mappers> • 1 • 2 • 3
4.3 接口式编程的使用步骤
SqlSession session = sqlSessionFactory.openSession(); try { //获取接口的实现 EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); //进行CRUD } finally { session.close(); }
SqlSession由于不是线程安全的,因此不能作为静态变量或实例变量,而应该在每个方法中单独获取,并且使用完成之后关闭。不可以在多个方法中共享sqlSession!!!
4.4 测试代码
public class EmployeeMapperTest { private SqlSessionFactory sqlSessionFactory; { String resource = "mybatis.xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { throw new RuntimeException(e); } sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void getAll() { SqlSession session = sqlSessionFactory.openSession(); try { //com.sun.proxy.$Proxy5 implements com.atguigu.mybatis.mapper.EmployeeMapper //使用Mybatis提供的动态代理技术,获取接口的一个实例 EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); //class com.sun.proxy.$Proxy5 System.out.println(mapper.getClass()); //[interface com.atguigu.mybatis.mapper.EmployeeMapper] System.out.println(Arrays.toString(mapper.getClass().getInterfaces())); //进行CRUD mapper.getAll().forEach(System.out::println); } finally { session.close(); } } @Test public void selectOne() { SqlSession session = sqlSessionFactory.openSession(); try { EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); //CRUD Employee emp = mapper.getEmpById(1); System.out.println(emp); } finally { session.close(); } } @Test public void delete() { SqlSession session = sqlSessionFactory.openSession(true); try { EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); //CRUD mapper.deleteEmpById(1); System.out.println("删除成功"); //手动提交事务 //session.commit(); } finally { session.close(); } } @Test public void update() { SqlSession session = sqlSessionFactory.openSession(true); try { EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); //CRUD Employee e = mapper.getEmpById(3); e.setLastName("hahaha"); e.setEmail("xixixi"); mapper.updateEmp(e); } finally { session.close(); } } @Test public void insert() { SqlSession session = sqlSessionFactory.openSession(true); try { EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); //CRUD mapper.insertEmp(new Employee(null, "jack", "a", "b")); } finally { session.close(); } } }
1、获取所有员工测试截图
2、根据id获取员工信息
3、根据id删除员工
4、更新员工信息
5、插入员工信息
4.5 注解接口式编程
如果注重效率,也可以直接在Mapper的方法上标注相应的注解从而替代mapper.xml
的效果。
public interface EmployeeMapper2 { //根据id查询员工 @Select(" select * from employee where id = #{ageakljga} ") Employee getEmpById(Integer id); //增删改 写操作 不需要返回值 @Delete("delete from employee where id = #{xxxx}") void deleteEmpById(Integer id); @Insert(" insert into employee(last_name,gender,email) values(#{lastName},#{gender},#{email})\n") void insertEmp(Employee employee); @Update(" update employee set last_name = #{lastName} , gender = #{gender} ,email = #{email}\n" + " where id = #{id}") void updateEmp(Employee employee); @Select(" select * from employee ") List<Employee> getAll(); }
添加注解之后,同样需要再全局配置文件中,配置sql语句所在的Mapper接口全类名
<mapper class="com.atguigu.mybatis.mapper.EmployeeMapper2"/> • 1
5、ResultMap
当要封装的Bean的属性名和查询的方法无法一样对应时,就需要使用resultMap来自定义列名和Bean中字段的映射规则。
例如有如下查询方法
List<Emp> getAllEmp(); • 1
其中查询的Bean结构如下:
@Data @NoArgsConstructor @AllArgsConstructor public class Emp { private Integer id; private String name; private GenderAndEmail genderAndEmail; } @Data @NoArgsConstructor @AllArgsConstructor public class GenderAndEmail { private String gender; private String emailAddr; }
此时Bean的字段名与所查询表的相关列名有很大差别。使用resultType直接封装的话,有很多字段是无法正常封装的。我们使用resultMap自定义映射规则。
<select id="getAllEmp" resultMap="empMapRule"> select * from employee </select> <resultMap id="empMapRule" type="com.atguigu.mybatis.beans.Emp" autoMapping="true"> <result property="name" column="last_name"/> <association property="genderAndEmail" javaType="com.atguigu.mybatis.beans.GenderAndEmail" autoMapping="true"> <result property="emailAddr" column="email"/> </association> </resultMap>
6、引入Sql模板
在MySQL的Sql配置文件中,可以使用标签来定义sql,之后使用标签来引入sql,这样可以将高频使用的sql模板进行复用。
如下:
<select id="rowsOfTable" resultType="int"> select count(*) <include refid="sql1"/> </select> <sql id="sql1"> from employee </sql>
7、获取接口方法入参
在sql的xml中,可以使用${}和#{}来获取方法中传入的参数。
${参数名}可以直接获取指定参数名的值,不做任何处理 (一般用与表名传参)
#{ 参数名}在获取指定参数后,还会对类型进行判断,进行相应的处理,例如为String类型的参数自动添加引号(一般用于参数传参)。
在使用@Param注解可以在接口方法有多个参数时,为参数自定义参数名。
8、Sql注入
sql注入通常指的是恶意利用sql语句拼接的漏洞,注入非法的查询条件,来非正常获取数据的行为。例
如:
@Select("select * from employee where gender = ${gender}") List<Employee> getEmpsByCondition(@Param("gender") String gender); • 1 • 2
此时,在调用时,模拟sql注入如下:
List<Employee> emps = mapper.getEmpsByCondition("'male' or id > 0 "); • 1
后果是,数据库中的所有用户信息都将被非法获取。所以在参数位置,我们要尽量避免使用${}而使用#{}
9、动态SQL
有时候,我们需要根据前台传入的参数动态的去拼接sql的过滤条件,如果使用${}去拼接sql,又会面临着sql注入的风险。所以,此时,可以使用MyBatis的动态sql技术,既可以满足拼接sql的需求,又可以避免sqk注入的风险。
示例:
List<Employee> queryByCondition(@Param("name") String name, @Param("id") Integer id, @Param("gender") String gender);
sql语句如下:
<select id="queryByCondition" resultType="com.zhm.mybatisplusdemo.beans.Employee"> select * from emp <where> <if test=" name != null "> and last_name = #{name} </if> <if test=" id != null "> and id = #{id} </if> <if test=" gender != null "> and gender = #{gender} </if> </where> </select>
其中标签可以在sql中直接生成一个where子句的标识符,还可以将where中不符合语法规则部分多余的and或or自动去除。但是需要注意的是,必须把and和or编写在语句的前列。
测试观察生成的sql:
mapper.queryByCondition(null,null,"a"))
您的支持是我创作的无限动力
希望我能为您的未来尽绵薄之力
如有错误,谢谢指正若有收获,谢谢赞美