范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

一文搞懂MyBatis的使用

  在学习Java的过程,我们可能使用了JDBC来访问数据库获取数据,但是JDBC也有比较大的问题sql语句写在java程序中,需求修改的话则需要修改Java代码sql占位符的拼接传递比较麻烦数据库对象映射成Pojo也比较麻烦
  MyBatis这个框架通过对JDBC的封装,开发者只需要按照约定的配置进行配置开发,就可以方便的进行ORM操作
  O(Object),R(Relational),M(Mapping):对象关系映射,大白话就是Java对象映射成数据库某张表中的一行记录,一行记录又可映射成一个Java对象开始上车
  安装Mybatis,传统的模式自然免不了找到对应的Jar包添加成依赖,但通过Maven工具可以方便的控制项目依赖,需要在pom.xml中添加对应依赖,关于Maven这里不介绍过多,不知道GVC怎么写的可以在mvnrepository.com/ maven仓库中搜索对应的即可,比如搜索MyBatis,选择对应的版本,复制如图所示的依赖配置到pom.xml中,刷新maven依赖即可下载,这里除了Mybatis还需要下载mysql-connector,Java链接数据库需要通过驱动,不同厂商有各自的实现
  创建一个Maven项目,创建过程忽略
  这里以Mysql为例子,在maven依赖中添加如下依赖,一个是mybatis,一个是mysql的驱动                     org.mybatis             mybatis             3.5.11                               mysql             mysql-connector-java             8.0.30          复制代码按照官网提供的配置进行本地化配置先配置一份mybatis的主配置文件下面的配置为最简化的配置,需要替换掉dataSource中的四个标签值,并且这里我选择了创建一个jdbc.properties文件用来管理数据库链接信息,通过properties标签的resource属性进行导入,需要注意文件的存放位置,默认会从resource目录开始找(如果不用这种方式,直接写上一个字符串即可)<?xml version="1.0" encoding="UTF-8" ?>                                                                                                复制代码
  jdbc.properties创建,自行替换driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/db username=root password=12345678 复制代码
  UserMapper.xml创建,这里需要说明一下,mapper标签中的namespace属性,作用类似于唯一包名,主要用于区分不同mapper文件中,增删改查标签中id相同的情况,如有多个mapper文件,里面的查询标签id一致,但是加上namespace.进行拼接就可以区分。这里主要演示mybatis,对应的表设计可和sql自行处理,这里提供参考(学习Mybatis的使用才是文章内容,不了Mysql和Sql的应该先去了解)
  <?xml version="1.0" encoding="UTF-8" ?>                insert into t_user             (`username`, `password`, `age`, `gender`)         values ("ben", "123", 40, "0")       复制代码
  目前resources目录下应该有如下文件
  编写一个测试类来验证一个最基础的mybatis使用需要创建一个SqlSessionFactoryBuilder对象通过SqlSessionFactoryBuilder对象的build方法创建SqlSessionFactory对象,需要传入一个InpuStream类型的参数,实际上是指上面编写好的mybatis-config.xml通过SqlSessionFactory的openSession方法创建SqlSession对象,方法可以接受一个boolean参数,由于配置中配置的是JDBC的事务管理器,所以事务默认是不提交的,可以通过传入true来开启自动提交(可以理解为执行一次增删改就自动提交一次),不传或传入false则表示开启事务(得手动提交)由于需要执行的是插入数据操作,所以调用sqlSession.insert方法(会有对应的update和delete方法等),需要传入一个唯一id,这个id指的就是UserMapper.xml中mapper标签下配置的insert标签的id,如果成功,则会返回影响的数据条数public class Main {     public static void main(String[] args) throws IOException {         SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();         SqlSessionFactory build = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));         // SqlSession sqlSession = build.openSession(true); // 如果传入true则开启自动提交         SqlSession sqlSession = build.openSession();         try{             // 像目前只有一个mapper文件,可以不用加前缀,直接insertUser即可,否则最好加上namespace避免重名             int count = sqlSession.insert("user.insertUser"); // 会返回影响的条数             if (count == 1) {                 System.out.println("插入成功成功");             }else{                 System.out.println("插入数据失败");             }         	sqlSession.commit(); // 记得要提交事务         }catch(Exception e){             // 抛出异常时事务回滚             if(sqlSession != null){                 sqlSession.rollback();             }         }finally{             // 结束时关闭资源             if(sqlSession != null){                 sqlSession.close();             }         }     } } 复制代码
  当执行程序后,可以看到数据库成功增加了一条记录
  关于SqlSessionFactoryBuilder的入参输入流,代码演示使用了mybatis提供的一个工具类,如果你想,也可以替换成任何自己实现的InputStream对象,只要能创建即可new FileInputStream("xxxx");ClassLoader.getSystemClassLoader().getResourceAsStream("xxxx");实际上,MyBatis提供的方法底层就是通过这个ClassLoader对象进行调用的,原理是从类路径开始查找对应资源...任何产出InputStream的方式关于日志配置
  日志的输出可以让开发过程更清晰的看到SQL的拼接执行等信息,这里需要十分注意settings标签的顺序位置,错了的话会报错,具体顺序报错信息会提示你                             复制代码
  这里使用了mybatis内置的标准日志实现,添加配置后再次执行,就能看到如图所示的日志信息,方便开发调试
  亦可配置如SLF4J,Log4j等,其中的区别可能是性能或配置需求等,自行抉择动态插入
  上面的实例中,插入语句是写死的值,显然实际开发中对应的值是需要前端将用户注册的参数穿过来,此时可以使用#{}进行占位,作用等价于JDBC中的?占位符(底层用的是PreparedStatement没有SQL注入问题)执行对应方法时,传入Map集合传入Pojo对象,但是需要编写对应的getter方法测试insert传入Map
  先改写mapper.xml,将原本写死的地方改成#{Map的key名}              insert into t_user             (username, password, age, gender)         values (#{username}, #{password}, #{age}, #{gender})       复制代码public class Main {     public static void main(String[] args) throws IOException {         // 简单的工具类封装了SqlSession         SqlSession sqlSession = SqlSessionUtil.openSqlSession();         Map user = new HashMap();         user.put("username", "jack");         user.put("password", "jinwandalaohu");         user.put("age", 60);         user.put("gender", "0");         int count = sqlSession.insert("user.insertUser", user);         if (count == 1) {             System.out.println("插入成功");         } else {             System.out.println("insert failed");         }         sqlSession.close();     } } 复制代码
  再次执行,可以看到#{}被替换成了?,并且日志输出了每个占位符对应的值和数据类型,并且数据成功插入
  需要注意的是,如果使用Map当参数传入,占位符填写了不存在的key,则最终插入的值会是null测试传入Pojo类
  先建立一个User类,比较关键的地方在于,需要编写对应的getter方法public class User {     private Long id;     private String username;     private String password;     private Character gender;     private Integer age;      public User() {     }      public User(String username, String password, Character gender, Integer age) {         this.username = username;         this.password = password;         this.gender = gender;         this.age = age;     }      public Long getId() {         return id;     }      public void setId(Long 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;     }      public Character getGender() {         return gender;     }      public void setGender(Character gender) {         this.gender = gender;     }      public Integer getAge() {         return age;     }      public void setAge(Integer age) {         this.age = age;     } } 复制代码
  Usermapper.xml的内容不需要修改public class Main {     public static void main(String[] args) throws IOException {         SqlSession sqlSession = SqlSessionUtil.openSqlSession();         User user = new User("Susan", "qweasd", "1", 29);         int count = sqlSession.insert("user.insertUser", user);         if (count == 1) {             System.out.println("插入成功");         } else {             System.out.println("insert failed");         }         sqlSession.close();     } } 复制代码
  执行后依然可以看到插入成功
  使用Pojo类和Map最大的区别点在于,必须传入存在的getter方法名,举个例子,Mybatis会把getUsername的get去掉,首字母U改成小写的u,最终占位符传入的就是username,如果不存在getter则会报错,而不是传入null
  在Mapper的编写中,有一个parameterType参数类型可以传入全限定类名,但一般这个可以忽略不写,如果要写格式如下
  动态删除
  在UserMapper.xml中,添加一个delete标签,由于传参是个简单数据类型,且只有一个参数,占位符的key可以随便写              delete from t_user where id = #{suibiannixie}       复制代码
  调用sqlSession的delete方法,传入idpublic class Main {     public static void main(String[] args) throws IOException {         SqlSession sqlSession = SqlSessionUtil.openSqlSession();         int count = sqlSession.delete("user.deleteUser",8);         if (count == 1) {             System.out.println("删除成功");         } else {             System.out.println("delete failed");         }         sqlSession.close();     } } 复制代码
  可以看到8被成功替换了#{suibiannixie}
  动态修改
  这里演示只根据用户id修改用户名        update t_user set username=#{username} where id = #{id}     复制代码
  调用sqlSession的update方法,传入User对象public static void main(String[] args) throws IOException {     SqlSession sqlSession = SqlSessionUtil.openSqlSession();     User user = new User();     user.setUsername("tester");     user.setId(14L);     int count = sqlSession.delete("user.updateUser",user);     if (count == 1) {         System.out.println("更新用户名成功");     } else {         System.out.println("update failed");     }     sqlSession.close(); } 复制代码
  更新成功如下
  使用Mybatis查询数据
  与增删改有点区别,他们都是返回数据表中被影响的数据行数,而查询则会返回一个封装好的查询结果集,比如查询用户表,那么返回结果就是单个用户对象,或者用户对象的集合
  编写mapper,图方便用*     复制代码
  执行sqlSession的selectOne方法public static void main(String[] args) throws IOException {     SqlSession sqlSession = SqlSessionUtil.openSqlSession();     Object user = sqlSession.selectOne("user.selectById", 1);     System.out.println(user);     sqlSession.close(); } 复制代码
  执行程序后可以看到如下报错
  意思就是mybatis检测到你没有为select标签提供对应的结果映射类型,不提供的话它没有办法知道如何处理对应的结果集,再次修改mapper,添加一个resultType标签,填写全限定类名     复制代码
  再次执行便能查到对应用户信息,这里需要注意一下几点当用户表字段用Pojo类命名格式不一致
  为了演示效果,为用户表额外添加一个underScoreCase(下划线命名)的字段,并给对应数据添加日期类型的值
  Pojo类中添加对应的字段和getter方法,java命名规范是小驼峰
  此时再次执行查询程序,可以看到对象映射并没有成功,对应的createdAt字段为null
  原因就是因为属性名没有对上,有以下几种解决方案通过sql编写别名,让查询结果字段名对的上created_at as createdAt 复制代码通过mybatis提供的设置选项,开启驼峰命名自动映射,前提是转换后的名字能对得上,如数据库的字段为a_b,转化后就成了aB                            复制代码通过select标签的resultMap映射(后面会说)
  前两种方式任选其一配置后,再次执行查询语句,可以看到createdAt字段已经有值了
  查询多条数据
  sqlSession查多条就需要调用selectList方法,虽然返回的是List集合,但resultType仍然指定实体类名,新增一个select标签     复制代码
  调用selectList方法,接受类型为User集合public static void main(String[] args) throws IOException {     SqlSession sqlSession = SqlSessionUtil.openSqlSession();     List users = sqlSession.selectList("user.selectAll");     users.forEach(System.out::println);     sqlSession.close(); } 复制代码
  查询成功
  实际上,查询结果为单条的情况下也可以使用List进行接收使用接口代理形式
  上述的使用方式有一个非常大的问题在于,开发者需要记住且拼接对应的映射id,这种方式并不便于开发维护,所以Mybatis还提供了一个更为方便使用的代理模式
  首先新建一个接口,这里命名为UserDaopublic interface UserDao {     int insertUser(User user);      int deleteUser(Long id);      int updateUser(User suer);      User selectById(Long id);      List selectAll(); } 复制代码
  接着配置对应的mapper,这里需要注意的是,namespace不能再乱写了,需要写成UserDao的全限定类名,并且所有sql标签的id属性也要和接口中的方法名保持一致,如果不一致,则会抛出对应的异常        insert into t_user     (username, password, age, gender)     values (#{username}, #{password}, #{age}, #{gender})           delete from t_user where id = #{suibiannixie}           update t_user set username=#{username} where id = #{id}           复制代码
  调用执行的时候不再使用namespace拼接id的方式,而是使用sqlSession的getMapper方法,传入对应的Mapper接口的类对象,mybatis底层实现会去进行解析反射等一系列操作,最终生成一个代理对象,开发者只需要调用接口中的方法即可完成数据库操作,实现的效果完全等价的public static void main(String[] args) {     SqlSession sqlSession = SqlSessionUtil.openSqlSession();     UserDao mapper = sqlSession.getMapper(UserDao.class);     List users = mapper.selectAll();     users.forEach(System.out::println);     sqlSession.close(); } 复制代码
  这样的方式更为常用关于#{}和${}
  学过JDBC开发的都知道,有个PreparedStatement和Statement对象#{}底层实现对应着PreparedStatement,有防SQL注入的作用,先编译再进行占位符替换,上面所有的示例中都是使用这种方式,用的最多${}底层对应着Statement,有SQL注入的风险,一般在SQL语句需要对保留关键字进行拼接的情况下使用,能不用就不用举几个需要使用${}的情况查询结果集需要根据username排序,排序方式需要自定义传入,编写如下SQL 复制代码
  这种情况下才适合使用,引用如果使用#{},会将传入的asc或者desc加上字符的引号,那么对应的语句便不合法,因为关键字是不需要加引号的表名不能写死的情况下,需要动态修改执行,可以使用,原理还是引号导致的语法问题SQL语句使用了in(????),如delete from xxx where id in (${keys})此时也可以使用,原理同上模糊查询的情况下,如select * from xxx where username like "%${key}"
  模糊查询下如果不想使用${},可以选择#{},但是需要一定拼接技巧使用concat函数,语句可以这样写select * from xxx where username like concat("%",#{key},"%"),最终出来的效果就是合法的语句使用双引号包括%,select * from xxx where username like "%"#{key}"%"typeAliases 类型别名
  主要用于给数据类型起别名,在mybatis-config.xml中进行配置,需要注意顺序,在后续的mapper文件中,指定类型时就不再需要写冗长的全限定类名,在使用的时候是不区分大小写的                   复制代码
  像这个查询语句的resultType,原本写的全限定类名就可以替换成简短的别名     复制代码
  此时如果一个类名配置一次显然也会麻烦,恰好提供了一个package标签,只需要指定包名,那么这个包下的所有类都会生成一个对应的简类名别名供使用                   复制代码Mybatis配置文件配置项理解
  其实这一环在官网的文档中有说明,遇到具体的配置问题可以随时查询 mybatis.org/mybatis-3/z…
  下面只简单的讲解properties
  提供了更灵活的配置使用,像上述demo中就通过资源文件动态配置了JDBC信息数据,可以使用resource或者url属性进行文件配置导入,也可使用子标签配置属性前提是资源文件在类路径下才能找到资源environments
  用于配置环境信息,有一个default属性,可以用于执行采用那个environment标签的配置信息,需要和对应标签的id一致environment,这里配置的是具体的数据源信息,其中的id属性就是给其父标签environments的default属性使用的一个environment可以理解为就是一个环境,对应着一个SqlSessionFactory对象transactionManager
  用于配置事务管理器,其中有个type属性可供使用JDBC,配置为这个值,则采用JDBC的事务机制,相当于默认开启事务并且不会自动提交,需要开发者手动的进行commit事务MANAGED,配置这个值会将事务机制交给其他容器管理,如果没有对应容器则没有事务,执行一次DML(增删改查)就提交一次dataSource
  用于指定数据源相关的信息,此标签有一个type属性可以配置连接池的策略,不同的type决定着子标签的property可以配置不同的属性值UNPOOLED 采用最传统的方式获取数据库链接,并且不会有连接池的概念,每一次访问都是新的链接driver 配置jdbc驱动的java全限定类名 url配置数据库的jdbc地址 username 配置数据库用户名 password 配置数据库用户密码 defaultTransactionlsolationLevel 配置默认的事务隔离级别(此处为Mysql的知识点) defaultNetworkTimeout 配置等待数据库操作的默认网络超时时间POOLED 采用了连接池的规范实现上述UNPOOLED策略列出的所有都可以配置 poolMaximumActiveConnections 在任意时间可以使用的最大连接数量,默认值是10,若一个请求占用一个线程,此时有20个请求,同一时间内只能处理10个请求,线程就会占满,第11个请求只能等待空闲线程 poolMaximumldleConnections 任意时间可能存在的空闲连接数,默认值是5,如果已经有个5个线程是空闲状态,当出现第6个空闲线程时,会将此线程关闭减少数据库开销 poolMaximumCheckoutTime 强制回归线程池时间,默认值为20s poolTimetoWait 当无法获取到空闲连接时,每20s打印一次日志 其他不太常用property
  该标签提供了更为灵活的配置方式,如将配置文件用其他文件格式的资源文件进行管理,上面第一个例子就是使用了引入外部资源的方式,配置resource属性mapper
  用于指定SQL映射文件路径通过引入类路径下的资源文件     复制代码通过file:///格式的url进行查找,一般不会使用这个,移植性太差通过Class接口的全限定类型进行配置,mybatis底层会进行动态代理,假设有这么一个UserMapper类,配置完后会自动进行映射,前提是对应的namespace和id等需要与类名及方法名一一对应上,还有最关键一点:xml文件需要和接口类放到同一个目录下     复制代码通过配置包名,这个是基于方式3的前提下,更便捷的配置映射,原本使用的mapper标签更改为package标签,整个包下的接口都会批量的进行映射     复制代码传入数据后获取到自增的id
  正常情况下,执行一个新增方法,会传入一个对应的实体类,但一般这个实体类是没有id的,如果需要插入成功后数据表中对应的自增id,要么自己根据条件再查询一遍获取,或者可以通过配置,在insert标签中开启useGeneratedKeys和指定propertKey             insert into t_user             (username, password, age, gender)         values (#{username}, #{password}, #{age}, #{gender})      复制代码
  后续的使用中,没有添加上述属性时,传入的User实例是不会有id属性的,配置完了再次执行查看输出public static void main(String[] args) {     SqlSession sqlSession = SqlSessionUtil.openSqlSession();     UserDao mapper = sqlSession.getMapper(UserDao.class);     User user = new User("ma", "!23", "1", 20);     mapper.insertUser(user);     System.out.println(user);     sqlSession.close(); } 复制代码
  可以看到id成功被赋值了
  Mybatis的参数处理
  使用的代码示例当中,语句标签的parameterType上面说过可以省略,sql中使用的#{key}实际是个省略写法,他的完整写法如下...sql忽略... where name=#{name, javaType=String, jdbcType=VARCHAR} 复制代码
  一般情况是都忽略不用写的,像7种基本数据类型以及对应的包装类,还有String,Date这些参数类型,mybatis会自己进行类型推导,最终由推导结果来觉得调用JDBC中的setString,setInt等等多参数处理
  之前演示的都是单参数形式,多参数的处理有一些规则需要遵守,先编写一个需要传入两个参数的sql及方法 复制代码
  新增一个接口方法User giveTwoParameter(String username,Integer age); 复制代码
  执行方法,传入一个姓名和年龄    public static void main(String[] args) {         SqlSession sqlSession = SqlSessionUtil.openSqlSession();         UserDao mapper = sqlSession.getMapper(UserDao.class);         mapper.giveTwoParameter("ma",20);         sqlSession.close();     } 复制代码
  不出意外的话可以看到报错,提示username找不到,可以用的占位符key有 [arg1, arg0, param1, param2],需要说明一下,如果使用arg则后面的序号从0开始,如果使用param,则后面的序号从1开始
  只需要按照提示,将原本的#{username}和#{age}替换成arg格式或param格式的即可
  实际上,mybatis在底层实现中,有一个Map集合进行数据收集Map map = new HashMap<>(); map.put("arg0",key); map.put("param1",key); // 每个参数都以此类推 复制代码@Param注解的使用
  MyBatis在代理生成对象时,会去检查入参的参数是否带有@Parmas的注解,如果有,则会新建一个Map集合用来存放最终的key and value,当使用了这个注解后,等价于把默认的arg格式的key给替换成我们自定义的key,但param格式的key还保留着。修改接口中的参数,添加对应的注解值    User giveTwoParameter(@Param("username") String username,@Param("age") Integer age); 复制代码
  对应的UserMapper.xml中占位符使用注解传入的key即可可以尝试用Map去接受返回值
  当不知道用什么对应的实体类接收SQL的返回值时,可以用Map类型,Mybatis会自动转换成键值对的形式,但一般不建议这么做,可读性太差了当数据库字段与Java对象属性无法匹配时可以通过sql语句的as关键字对需要查询出数据的列名进行重命名成Java属性对应的name上面演示过的通过配置setting标签中的,驼峰转换自动映射,前提是数据库的列名和Java的属性名都遵循规范,如字段名为user_name,则对应的Java属性名为userName     复制代码通过定义resultMap来手动映射,以示例中出现过的的User类举例                                                                               复制代码
  这里的id为配置主键,其他的属性用result标签,其中property为Java对象的属性,column为查询结果集的字段名,后续在指定select标签的返回值时,不再指定resultType,而是指定resultMap,传入的就是定义map时的idMybatis内置的类型
  像一些常规的int、long,map,string等类型,不需要定义就可以在resultType中使用,这是因为框架内置了映射,具体的内置配置可以查看 mybatis.org/mybatis-3/z…,翻到类型别名处,文档又说明Mybatis比较高级的特性--动态SQL
  所谓的动态,就是能方便开发者进行一些有条件及便携的sql语句拼接if标签
  以一个常见的场景来说,需要查询某个数据列表,可以提供一些查询条件,这些条件可传可不传,此时就可以使用if标签了     复制代码
  这里where语句后面的1=1是为了保证即使if标签的条件没有通过,也不会报语句错误。当传入的User对象的gender对象不为空,则会进行拼接,否则不会,执行到1=1就结束了where标签
  在上面的例子中,为了不报错需要书写1=1这种特殊处理,where标签能够更智能的处理这种情况在where标签下的if标签,如果条件不通过,则不会生成子句会自动去掉某些条件前多余的and或者or,参考上面if标签的使用中and gender = #{gender}中的and
  利用where标签改造一下     复制代码
  此时若过gender为null,最终的sql为select * from t_user,反之则是select * from t_user where gender = #{gender},这里的and会被智能的去掉,如果有多个子标签,则and会被保留trim标签
  个人用的不太多,主要作用是可以在语句前后做增加和删除prefix:添加前缀suffix:添加后缀prefixOverrides:删除前缀suffixOverrides:删除后缀
  简单的演示下使用     复制代码
  当if标签中的条件不满足时,也会智能不处理,若满足条件,则在包括的语句前添加prefix,并且删除掉语句末尾的suffixOverridesset标签
  一般用于update语句中,用起来其实和where标签很类似   update t_user               username = #{username},                 gender = #{gender},          复制代码
  上面的语句中,实现的需求就是当username和gender不是null或者空字符串时,才会拼接set语句,并且会去掉子句后面的,。这里有个问题就是当二者都不满足条件,就会产出一条错误的sql语句,更推荐用if+手写sql的方式来保证sql语句的正确性choese when otherwise
  这个其实就相当于if...else if...else的语法,都是配合着使用,还是以查询为例子,可以提供username,age的查询条件,当提供了username则使用username,如果没有提供username却提供了age,则使用age,否则则使用默认的查询条件 复制代码foreach标签
  可用于循环遍历数组或集合,一般常用与批量增删改操作,有以下属性可以使用collection:需要被循环的值item:被循环的每个元素可用别名index:循环的索引open:在子句生成的开头插入指定字符close:在子句生成的结尾插入指定字符separator:循环之间的分隔符
  以删除语句为例子,这里提供了两种写法,都可以实现批量删除             delete from t_user         where                      id = #{id}                             delete from t_user         where id in (                      #{id}                  )      复制代码
  需要注意的是,这里collection使用了ids,是因为对应接口定义时参数使用了@Param("ids") List ids,否则运行后会报错,并告知你应该使用arg0, collection, list这些底层映射的默认可用值,显然这些可读性不好,更应该指定map的key,目前的设置,可用的就变为了ids和param1
  像批量插入和修改都是同理的,只要是合法的sql语句就能利用foreach标签批量构建出预期值sql标签和include标签
  前者用来定义sql片段,后者用来引入到某个sql中,用批量删除的语句来演示                      where id in (                      #{id}                  )                   delete         from t_user      复制代码
  用法比较简单,可以根据需求来决定提取哪些是可以复用的语句,比如繁琐的字段名就很适合定义为片段Mybatis的高级映射
  假设当前新增了一个Dept类存储部门信息,且每个用户都有对应的部门,此时就形成了多对一的关系,那么User对象当中就应该多一个Dept类型的成员属性
  先定义一个部门类package cn.mgl.pojo;  public class Dept {     private Long dept_id;     private String dept_name;      public Long getDept_id() {         return dept_id;     }      public void setDept_id(Long dept_id) {         this.dept_id = dept_id;     }      public String getDept_name() {         return dept_name;     }      public void setDept_name(String dept_name) {         this.dept_name = dept_name;     } } 复制代码
  在数据库中也要创建对应的表结构CREATE TABLE `t_dept` (                           `dept_id` int DEFAULT NULL,                           `dept_name` varchar(20) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci 复制代码
  除此之外还需要对原本User类添加一个成员属性dept,类型为Dept,并添加对应的getter,setter函数
  (相关的数据库数据自行添加)多对一映射的方式:一条外链接Sql映射结果                                                   复制代码
  这种用法的select标签不使用resultType而是resultMap,注意对应的写法:中的dept为User类中的属性名,不可以随便写,这里可以不需要把所有映射都写上,如果确定类属性和结果集列名匹配得上可以不写
  执行相关语句后,查询用户时也能把部门信息查出来并映射到成员嵌套对象中
  使用映射中的关联标签                                                                          复制代码
  只需要把原本的手动级联映射改为association标签,设置javaType为Dept类,接着配置类属性和字段名映射关系,实现效果和方式一完全一致分步查询
  所谓的分布查询就是将查询分为步骤一、步骤二...以此类推,比如这个实例中,可以先查询出用户表信息,这是第一步,由于用户表存储了部门id,所以可以通过查询出来的部门id再去查询相关部门信息,这是第二步
  新增一个DeptDao的接口,目前只写一个抽象方法package cn.mgl.dao;  import cn.mgl.pojo.Dept;  public interface DeptDao {     Dept getById(Long id); } 复制代码
  接着添加对应的deptMapper.xml,编写一条根据id查询信息的sql(需要在mybatis-config.xml添加mapper)<?xml version="1.0" encoding="UTF-8" ?>       复制代码
  改写原本UserMapper.xml编写的查询,不需要外链接,只写简单的单表查询,并且association中添加一个select属性,指定为namespace+id的格式,column属性则为子语句的查询条件                                                                                             		 复制代码
  执行查询后,可以看到执行了两条语句,并且会自动将第一步查询出来的dept_id当作查询参数传入第二条sql中
  这样做的好处:代码的复用性增强,耦合度降低支持延迟加载,访问不到的数据可以先不查询,增加查询效率看完多对一,接下来看看一对多利用collection标签
  其实区别只是主体不同,现在要改成查询部门下的全部用户,则主体变更为部门,那么此时部门的类中应该有一个用户类型的集合才存储该部门下的所有用户(下面省略Java代码编写,自行操作)                                                                             复制代码
  在resultMap中,有一个collection子标签,其中的property写了collection是因为在User类中有成员属性名就是collection,后面的ofType填写这个集合中类型,子标签的用法就大同小异了
  执行查询后,虽然sql结果集为4条,但会将最终映射出来的数据只有3条(同一个部门归类),且Dept类中的集合成功赋值
  和一对多类似的分步查询
  这个原理就一样了,先做第一步查出部门,再做第二步查出部门下所有用户                                                   复制代码
  与第一种方式的区别是,原本的连表查询改为单表查询,并且collection标签中新增select属性,对应的查询方法自行添加,实现的最终效果也完全一致延迟加载
  所谓的延迟加载就是没用到的时候不执行对应的sql,用到再执行,以减少查询的方式增加性能
  一对多与多对一中的延迟加载机制是一样的对某个查询单独使用fetchType="lazy",用上面的例子演示                                                   复制代码在全局配置中,添加一个setting标签,设置lazyLoadingEnabled="true",此时全部sql都会走延时加载                                     复制代码
  若在开启全局配置后,就是想要某个sql不走延时加载,只需要添加fetchType="eager"即可
  一般推荐全局开启,减少查询增加性能了解Mybatis的缓存机制
  如这次执行查询后,将查询结果放到内存中,如果下次还是这条查询语句,则直接从缓存中取,不需要再去数据库中查询,以此来提高效率性能,但是只对select语句有效一级缓存
  针对的事sqlSession
  下面的例子中:SqlSessionUtil.openSqlSession简单封装的获取sqlSession对象mapper.test是一条简单的selectSqlmapper.update是一条简单的updateSql        SqlSession sqlSession = SqlSessionUtil.openSqlSession();          UserDao mapper = sqlSession.getMapper(UserDao.class);         List test = mapper.test();         List test2 = mapper.test();         System.out.println(test2);         System.out.println(test); 复制代码
  运行后,select只执行了一次,第二次再次查询直接从缓存中取,所以没有sql执行日志输出
  下面演示的是一级缓存失效手动执行sqlSession的clearCache方法,sqlSession.clearCache();,比较简单不演示在同一个SqlSession的情况下,两条select语句之间执行了update,insert,delete任一,一级缓存会被清空,代码用update举例子        SqlSession sqlSession = SqlSessionUtil.openSqlSession();          UserDao mapper = sqlSession.getMapper(UserDao.class);         List test = mapper.test();         User user = new User();         sqlSession.clearCache();         user.setId(14L);         mapper.updateUser(user);         List test2 = mapper.test();         System.out.println(test2);         System.out.println(test); 复制代码
  运行后可以看到,即使再次执行相同的select,也不会走缓存
  不同SqlSession执行相同的select查询        SqlSession sqlSession = SqlSessionUtil.openSqlSession();         SqlSession sqlSession2 = SqlSessionUtil.openSqlSession();          UserDao mapper = sqlSession.getMapper(UserDao.class);         UserDao mapper2 = sqlSession2.getMapper(UserDao.class);          List test = mapper.test();         List test2 = mapper2.test();          System.out.println(test2);         System.out.println(test); 复制代码
  运行后可以看到执行了两次查询
  二级缓存
  这个设置需要满足一定的条件才会开启,且是针对SqlSessionFactory的,前面说过可以根据不同的环境获取不同的SqlSessionFactory,如有两个SqlSession,即使他们都执行一样的sql语句,也不会走二级缓存
  开启前提条件:mybatis-config.xml中,settings下的标签卡其了全局缓存,默认就是true,,相当于不配置就是开启在需要使用缓存的SqlMapper.xml中添加一个标签被缓存的pojo类必须实现Serializable接口,让其拥有可序列化特性sqlSession对象关闭或提交之后,一级缓存中的数据才会被写入二级缓存中,此时缓存才能体现
  前三步比较简单,直接看第四步的代码        SqlSession sqlSession = SqlSessionUtil.openSqlSession();          UserDao mapper = sqlSession.getMapper(UserDao.class);         List test = mapper.test();         System.out.println(test);         sqlSession.close();          SqlSession sqlSession2 = SqlSessionUtil.openSqlSession();         UserDao mapper2 = sqlSession2.getMapper(UserDao.class);         List test2 = mapper2.test();         System.out.println(test2); 复制代码
  运行后的输出,有一句Cache Hit Ratio且没有第二次sql日志输出,说明缓存命中了
  二级缓存失效情况和一级缓存一样,中间执行过select,update,insert语句时,缓存也会失效不同的sqlSessionFactory对象
  想测试的话比较简单,便不再演示查询分页Mybatis使用PageHelper插件
  这个插件是帮助处理分页需求的,正常情况下分页查询需要接上limit startIndex,pageSize进行查询,通过这个插件可以让开发过程减少一些重复冗余的分页编写
  通过maven添加依赖   com.github.pagehelper   pagehelper   5.3.2  复制代码
  接着在mybatis-config.xml中添加插件配置,注意顺序问题                   复制代码
  sql中不需要添加分页语句,只需要在查询语句执行前,调用一次startPage方法并传入相关参数,简单讲讲传参的计算,假设以10条数据为一页,查询第2页的数据,则需要传入2,10,后续的计算中则为(2-1)*10,10,最终结果就是limit 10,10public static void main(String[] args) {     SqlSession sqlSession = SqlSessionUtil.openSqlSession();     UserDao mapper = sqlSession.getMapper(UserDao.class);     PageHelper.startPage(2,10);     List users = mapper.selectAll();     users.forEach(System.out::println);     PageInfo tPageInfo = new PageInfo<>(users);     sqlSession.close(); } 复制代码
  简单的说下原理,执行了插件方法startPage后,会缓存分页数据到ThreadLoacl当中,只会对执行了startPage方法后的第一个查询语句起效
  如果不用分页插件也可以实现,先查询出表的总条数,再按照分页规则自行处理sql即可,插件只是帮忙处理了这几个步骤注解式开发
  mybatis除了xml以外,还提供了注解式开发,不过这种方式并不推荐使用,因为不好维护,除非是非常简单的sql语句,能用xml尽量用,不过也可以了解下使用方式,以基础的增删改查为例子,分别使用对应的注解,并传入sql的字符串,效果与xml文件配置是相同的public interface UserAnnoDao {     @Insert("insert into t_user (username, password, age, gender) values (#{username}, #{password}, #{age}, #{gender})")     int insertUser(User user);      @Delete("delete from t_user where id = #{id}}")     int deleteUser(Long id);      @Update("update t_user set username = #{username} where id = #{id}")     int updateUser(User suer);      @Select("select * from t_user where id #{id}}")     User selectById(Long id); } 复制代码
  完:)

八零后,是最好的一代,也是最悲催的一代八零后,随着改革开放而生,诞生于历史大变革的关键节点,沐浴着改革开放的春风,同时,长大后又见证了中国经济社会发展最迅猛的时代,并参与其中,可谓幸之又幸。人生几何,如白驹过隙,转瞬即乌拉圭00战平韩国队,意外看清1亿巨星!传球没谱踢球太独北京时间2022年11月26日,2022卡塔尔世界杯已经正式开始,并且比赛正在如火如荼的进行着,相信大家也跟小编一样,怀着无比激动的心情,急不可耐的想要第一时间接收到来自世界杯赛场悟道记人生经历如游历,生灵降生来修炼。感受真实且虚幻,如同饮水知冷暖。境随心转生万变,如梦初醒真玄幻。执念四起生业障,释然破执天地宽。生命究竟何意义,修心修身修大道。随心随性随情景,自由我的梦院内是房,我在房中点着灯,灯不算亮,在夜中显得昏暗,冷风从窗户缝涌了进来,我不理会。昏沉的灯下,温暖的棉衣,我渐困了,头愈发的沉重,我趴在桌上睡了。大钟点着时间,滴答的发着轻响。我28中的人千姿百态,28中的事丰富多彩有单纯善良错养别人家病子28年而家徒四壁如今手无寸铁且敢用生命丈量真相的有离开母体瞬间人生就被彻底改写如今仍身陷狼窝不能自己福祸突然从天降而茫然不知所措的有贪婪成性上蹿下跳掩耳盗铃世界杯快评以己之长蓝武士掀翻钢铁战车面对德国队球员硬如钢铁般的身躯,日本队发出的不再只是一声叹息。从01落后21反超,日本队凭借及时的战术调整和过硬的脚下技术,为自己赢来了本届世界杯的开门红。鉴于昨天沙特队爆冷战胜阿每日赛程梅西再度登场背水一战,阿根廷能否保留晋级希望?昨夜今晨,伊朗20威尔士!本届世界杯的第一张红牌诞生威尔士门将亨内西冲出禁区撞倒伊朗队员塔雷米,吃下一张红牌。卡塔尔13塞内加尔,小组赛提前一轮出局,成世界杯历史首支提前一轮出局的高管绿军是球员们买断后的心仪之地二登哥ampampampT罗斯有潜在可能直播吧11月26日讯近日,一位东部高管接受了Heavy记者的SeanDeveney的采访,谈到了凯尔特人。他说凯尔特人将是每个人(买断后)都想去的地方。他们有好的队友好的更衣室,球痛苦的奥秘解脱之道,道中解脱我想摆脱痛苦。什么是痛苦?不知道。那什么是快乐?快乐就是高高兴兴的,无忧无虑的,大概就是这样吧。快乐能不能留住?好像留不住吧,就那一阵子,过去就过去了。想留住快乐会如何?痛苦。那想揣一点温暖,入冬剑客明珠三剑客三剑客主播淅淅沥沥,纷纷扬扬当深秋的落叶遇上初冬的细雨,那份破碎的美感瞬间被拉满。我不自觉地来到窗前,一边搓着手,一边抬起头。明明觉得秋天才没来多久,怎么转眼已是冬天理性善良爱生活我不知道最近怎么了,解决问题的方式变得越来越暴力了。我们科技在发展,人民素质在提高,怎么解决问题的方式反而越来越原始了呢?我一直觉得暴力不是解决问题的途径,如果遇到事情你去游行示威
曼联32逆转!滕哈格神换人赢球却显无奈,两人低迷恐失主力欧联杯E组第3轮,曼联做客塞浦路斯尼科西亚的GSP球场对阵奥莫尼亚,红魔因自身失误首先丢球,替补上阵的拉什福德梅开二度,加上马夏尔的进球,最终32险胜,C罗送出赛季首次助攻。取胜的U17亚洲杯主力轮休为最后一战蓄力中国队11球大胜锁定小组前两名在当地时间10月7日下午,北京时间10月7日中午进行的第20届U17亚洲杯预选赛G组第2轮角逐中,2006年龄段中国U17队兵不血刃,以11比0超大比分战胜北马里亚纳群岛队,取得两C并口开发大全例子包含17个通道分析器程序员IT教育软件开发IT锐英源精品原创,禁止全文或局部转载,禁止任何形式的非法使用,侵权必究。点名简易百科和闲暇巴盗用锐英源原创内容。背景最近开发串口通信软件,在codeproj房价跌了一年了,但如今我们还是买不起,很可能也租不起了今年以来,房价大跌的说法一直流传在坊间!全国人都翘首以盼!从全国成交均价大跌,到三四线城市房价腰斩环京楼市暴跌,深圳炒房客被套,再到上海受疫情影响,人员大量外流,房价暴跌的预期不断华为高管,最大营收一把手骤逝华为高管,监事会副主席丁耘在连续跑完28公里的长跑后,于10月7日骤然离世,享年53岁。据悉,丁耘曾是华为公司常务董事会成员,今年4月份华为换届后,调整为华为监事会副主席,成为华为计算机专业真的还有前景吗?今年一季度,我国经济同比实质性提升了4。8,延续了之前几个季度的复苏势头。但第二季度由于疫情频发的影响,上海深圳等多地封城,消费投资和进出口等领域均受到冲击,国民经济增长率放缓至0去年富安百货倒闭,今年大洋百货关门,重庆杨家坪商圈没落了吗?国庆的前一天,杨家坪商圈突然传出一个重磅新闻,重庆大洋晶典发布公告开了十几年的大洋百货,将于十月底正式关闭了!看了这则公告,真是让人唏嘘不已。去年底的时候富安百货刚关闭,仅仅几个月阿联酋数字支付高速发展无现金交易的迅猛发展提升了阿联酋公共及私营部门的效率,为民众提供了高效便捷安全可靠的支付系统,对整个社会的发展促进普惠金融发挥了积极作用。消费习惯的变化导致阿联酋的现金使用量持续下家族信托合纵圈渠道本报记者樊红敏北京报道家族信托业务拓展,正在从得客户者得天下转向得渠道者得天下。导致上述布局的核心逻辑在于,当前留给信托公司的转型之路只有一条回归本源,而回归本源,就意味着要做与受彩电大王黄宏生的跨界造车梦本报记者郭阳琛张家振上海报道随着小米华为恒大和富士康等不同领域的龙头企业先后入局,跨界造车俨然已成为国内汽车行业中的一股潮流。从在江苏省南京市创立开沃新能源汽车集团股份有限公司(以三星宣布投产GDDR7显存带宽速度大幅提升对于显卡来说,除了GPU性能得给力之外,显存的速度同样需要不断地提升,以匹配GPU的传输速度,目前显卡中最快的速度为GDDR6X,大约是21Gbps上下,不过目前GPU的运算速度越