笔记参考与柏码知识库
JDBC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 try (Connection connection = DriverManager.getConnection("连接URL" ,"用户名" ,"密码" ); Statement statement = connection.createStatement()){ ResultSet set = statement.executeQuery("select * from 表名" ); while (set.next()){ ... } }catch (SQLException e){ e.printStackTrace(); }
DriverManager 用于管理数据库驱动。
添加日志打印:
1 2 3 static { DriverManager.setLogWriter(new PrintWriter (System.out)); }
Statement 用来执行各种数据库操作。
批处理操作 1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) throws ClassNotFoundException { try (Connection connection = DriverManager.getConnection(); Statement statement = connection.createStatement()){ statement.addBatch("insert into user values ('f', 1234)" ); statement.addBatch("insert into user values ('e', 1234)" ); statement.executeBatch(); }catch (SQLException e){ e.printStackTrace(); } }
登陆与SQL注入攻击 一般的登陆操作:
1 2 3 4 5 6 7 8 9 10 11 try (Connection connection = DriverManager.getConnection("URL" ,"用户名" ,"密码" ); Statement statement = connection.createStatement(); Scanner scanner = new Scanner (System.in)){ ResultSet res = statement.executeQuery("select * from user where username='" +scanner.nextLine()+"'and pwd='" +scanner.nextLine()+"';" ); while (res.next()){ String username = res.getString(1 ); System.out.println(username+" 登陆成功!" ); } }catch (SQLException e){ e.printStackTrace(); }
输入:
1 2 3 Test1111 ' or 1=1; -- # Test 登陆成功!
此时执行的SQL语句变为:
1 select * from user where username= 'Test' and pwd= '1111' or 1 = 1 ;
PreparedStatement 可以使用?
作为占位符,来将输入进行替换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void main (String[] args) throws ClassNotFoundException { try (Connection connection = DriverManager.getConnection("URL" ,"用户名" ,"密码" ); PreparedStatement statement = connection.prepareStatement("select * from user where username= ? and pwd=?;" ); Scanner scanner = new Scanner (System.in)){ statement.setString(1 , scanner.nextLine()); statement.setString(2 , scanner.nextLine()); System.out.println(statement); ResultSet res = statement.executeQuery(); while (res.next()){ String username = res.getString(1 ); System.out.println(username+" 登陆成功!" ); } }catch (SQLException e){ e.printStackTrace(); } }
管理事务 通过Connection对象调用setAutoCommit(false) 方法, 将SQL语句的提交(commit)由驱动程序转交给应用程序负责。也可以使用rollback()来手动回滚之前的全部操作, 回滚时可以设置回滚点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void main (String[] args) throws ClassNotFoundException { try (Connection connection = DriverManager.getConnection("URL" ,"用户名" ,"密码" ); Statement statement = connection.createStatement()){ connection.setAutoCommit(false ); statement.executeUpdate("insert into user values ('a', 1234)" ); Savepoint savepoint = connection.setSavepoint(); statement.executeUpdate("insert into user values ('b', 1234)" ); connection.rollback(savepoint); statement.executeUpdate("insert into user values ('c', 1234)" ); connection.commit(); }catch (SQLException e){ e.printStackTrace(); } }
Lombok 使用注解来简化数据的定义。
Getter与Setter 1 2 3 4 5 6 7 8 @Getter @Setter @AllArgsConstructor public class Student { private Integer sid; private String name; private String sex; }
1 2 @Accessors(chain = true) @Accessors(fluent = true)
Mybatis 使用xml文件来进行配置,减少代码量。
通过配置mybatis-config.xml
来配置数据库信息,并基于此创建SqlSessionFactory对象,并创建SqlSession就可以完成几乎所有的数据库操作。
概述 可以在项目根目录下新建名为mybatis-config.xml的文件,来配置需要连接的数据库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?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 ="${驱动类(含包名)}" /> <property name ="url" value ="${数据库连接URL}" /> <property name ="username" value ="${用户名}" /> <property name ="password" value ="${密码}" /> </dataSource > </environment > </environments > </configuration >
读取配置文件:
1 2 3 4 5 6 7 public static void main (String[] args) throws FileNotFoundException { SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(new FileInputStream ("mybatis-config.xml" )); try (SqlSession sqlSession = sqlSessionFactory.openSession(true )){ } }
读取实体类时,使用xml文件来建立映射规则。
实体类:
1 2 3 4 5 6 7 8 import lombok.Data;@Data public class Student { int sid; String name; String sex; }
在根目录下重新创建一个mapper文件夹,新建名为TestMapper.xml的文件作为映射器,填写映射规则:
1 2 3 4 5 6 7 8 9 <?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 ="TestMapper" > <select id ="selectStudent" resultType ="com.test.entity.Student" > select * from student </select > </mapper >
之后在配置文件中添加Mapper映射器:
1 2 3 4 <mappers > <mapper url ="file:mappers/TestMapper.xml" /> </mappers >
之后可以在程序中使用:
1 2 3 4 5 6 7 public static void main (String[] args) throws FileNotFoundException { SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(new FileInputStream ("mybatis-config.xml" )); try (SqlSession sqlSession = sqlSessionFactory.openSession(true )){ List<Student> student = sqlSession.selectList("selectStudent" ); student.forEach(System.out::println); } }
配置Mybatis 配置SqlSessionFactory 由于SqlSessionFactory一般只需要创建一次,因此我们可以创建一个工具类来集中创建SqlSession:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { sqlSessionFactory = new SqlSessionFactoryBuilder ().build(new FileInputStream ("mybatis-config.xml" )); } catch (FileNotFoundException e) { e.printStackTrace(); } } public static SqlSession getSession (boolean autoCommit) { return sqlSessionFactory.openSession(autoCommit); } }
之后进行查询:
1 2 3 4 5 6 public static void main (String[] args) { try (SqlSession sqlSession = MybatisUtil.getSession(true )){ List<Student> student = sqlSession.selectList("selectStudent" ); student.forEach(System.out::println); } }
namespace绑定接口 还可以将namespace与一个接口绑定,同时修改配置文件中mapper定义。
1 2 3 public interface TestMapper { List<Student> selectStudent () ; }
1 2 3 4 5 <mapper namespace ="com.test.mapper.TestMapper" > <select id ="selectStudent" resultType ="com.test.entity.Student" > select * from student </select > </mapper >
1 2 3 <mappers > <mapper resource ="com/test/mapper/TestMapper.xml" /> </mappers >
之后通过SqlSession来获取结果。
1 2 3 4 5 6 7 public static void main (String[] args) { try (SqlSession sqlSession = MybatisUtil.getSession(true )){ TestMapper testMapper = sqlSession.getMapper(TestMapper.class); List<Student> student = testMapper.selectStudent(); student.forEach(System.out::println); } }
配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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://localhost:3306/study" /> <property name ="username" value ="test" /> <property name ="password" value ="123456" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="com/test/mapper/TestMapper.xml" /> </mappers > </configuration >
可以使用多个环境,并通过id指定。
1 2 sqlSessionFactory = new SqlSessionFactoryBuilder () .build(new FileInputStream ("mybatis-config.xml" ), "环境ID" );
别名 可以给类型起别名来简化Mapper编写。
1 2 3 4 <typeAliases > <typeAlias type ="com.test.entity.Student" alias ="Student" /> </typeAliases >
1 2 3 4 5 <mapper namespace ="com.test.mapper.TestMapper" > <select id ="selectStudent" resultType ="Student" > select * from student </select > </mapper >
也可以直接让Mybatis去扫描一个包,并将包下的所有类自动起别名(别名为首字母小写的类名)
1 2 3 <typeAliases > <package name ="com.test.entity" /> </typeAliases >
也可以为指定实体类添加一个注解,来指定别名:
1 2 3 4 5 6 7 @Data @Alias("lbwnb") public class Student { private int sid; private String name; private String sex; }
增删改查 条件查询 1 Student getStudentBySid (int sid) ;
1 2 3 <select id ="getStudentBySid" resultType ="Student" > select * from student where sid = #{sid}</select >
尽量使用#{}而不是${},前者使用PreparedStatement首先进行一次预编译,有效地防止SQL注入问题,但后者是直接传值。
插入、更新、删除 更换标签即可
1 int addStudent (Student student) ;
1 2 3 <insert id ="addStudent" parameterType ="Student" > insert into student(name, sex) values(#{name}, #{sex})</insert >
复杂查询 一对多:
1 2 3 4 5 6 @Data public class Teacher { int tid; String name; List<Student> studentList; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="getTeacherByTid" resultMap ="asTeacher" > select *, teacher.name as tname from student inner join teach on student.sid = teach.sid inner join teacher on teach.tid = teacher.tid where teach.tid = #{tid}</select > <resultMap id ="asTeacher" type ="Teacher" > <id column ="tid" property ="tid" /> <result column ="tname" property ="name" /> <collection property ="studentList" ofType ="Student" > <id property ="sid" column ="sid" /> <result column ="name" property ="name" /> <result column ="sex" property ="sex" /> </collection > </resultMap >
多对一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Data @Accessors(chain = true) public class Student { private int sid; private String name; private String sex; private Teacher teacher; }@Data public class Teacher { int tid; String name; }
1 2 3 4 5 6 7 8 9 10 11 12 13 <resultMap id ="test2" type ="Student" > <id column ="sid" property ="sid" /> <result column ="name" property ="name" /> <result column ="sex" property ="sex" /> <association property ="teacher" javaType ="Teacher" > <id column ="tid" property ="tid" /> <result column ="tname" property ="name" /> </association > </resultMap > <select id ="selectStudent" resultMap ="test2" > select *, teacher.name as tname from student left join teach on student.sid = teach.sid left join teacher on teach.tid = teacher.tid</select >
事务操作 和JDBC差不多,这里使用SqlSession来进行事务操作。
1 2 3 4 5 6 7 8 9 try (SqlSession sqlSession = MybatisUtil.getSession(false )){ TestMapper testMapper = sqlSession.getMapper(TestMapper.class); testMapper.addStudent(new Student ().setSex("男" ).setName("小王" )); testMapper.selectStudent().forEach(System.out::println); sqlSession.rollback(); sqlSession.commit(); }
动态SQL if 使用if标签进行可选查询。
1 2 3 4 5 6 7 8 9 10 <select id ="findActiveBlogLike" resultType ="Blog" > SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <if test ="title != null" > AND title like #{title} </if > <if test ="author != null and author.name != null" > AND author_name like #{author.name} </if > </select >
choose(when, otherwise) 类似switch语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select id ="findActiveBlogLike" resultType ="Blog" > SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <choose > <when test ="title != null" > AND title like #{title} </when > <when test ="author != null and author.name != null" > AND author_name like #{author.name} </when > <otherwise > AND featured = 1 </otherwise > </choose > </select >
trim(where, set) where也可以作为标签。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select id ="findActiveBlogLike" resultType ="Blog" > SELECT * FROM BLOG <where > <if test ="state != null" > state = #{state} </if > <if test ="title != null" > AND title like #{title} </if > <if test ="author != null and author.name != null" > AND author_name like #{author.name} </if > </where > </select >
可以通过trim元素定制等价于where的元素。
1 2 3 <trim prefix ="WHERE" prefixOverrides ="AND |OR " > ...</trim >
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
set元素可以用于动态更新。
1 2 3 4 5 6 7 8 9 10 <update id ="updateAuthorIfNecessary" > update Author <set > <if test ="username != null" > username=#{username},</if > <if test ="password != null" > password=#{password},</if > <if test ="email != null" > email=#{email},</if > <if test ="bio != null" > bio=#{bio}</if > </set > where id=#{id}</update >
foreach
SELECT *
FROM POST P
WHERE ID in
#{item}
当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
缓存机制 分为一级缓存和二级缓存。一级缓存在进行DML操作后会失效,且只针对于单个会话。
二级缓存需要在映射器xml文件中手动添加。
1 2 3 4 5 <cache eviction ="FIFO" flushInterval ="60000" size ="512" readOnly ="true" />
可以添加useCache属性来为某个方法关闭缓存:
1 2 3 <select id ="getStudentBySid" resultType ="Student" useCache ="false" > select * from student where sid = #{sid}</select >
可以使用flushCache=”false”在每次执行后都清空缓存,还可以控制DML操作完成之后不清空缓存。
1 2 3 <select id ="getStudentBySid" resultType ="Student" flushCache ="true" > select * from student where sid = #{sid}</select >
读取顺序:二级>一级>数据库
如此使用缓存存在缓存一致性问题。可以通过关闭缓存来保证一致性。
1 2 3 <settings > <setting name ="cacheEnabled" value ="false" /> </settings >
1 2 3 <select id ="getStudentBySid" resultType ="Student" useCache ="false" flushCache ="true" > select * from student where sid = #{sid}</select >
使用注解 使用注解编写SQL语句可以进一步简化接口映射和绑定。
1 2 3 4 5 6 7 8 9 10 11 12 @Results({ @Result(id = true, column = "tid", property = "tid"), @Result(column = "name", property = "name"), @Result(column = "tid", property = "studentList", many = @Many(select = "getStudentByTid") ) }) @Select("select * from teacher where tid = #{tid}") Teacher getTeacherBySid (int tid) ;@Select("select * from student inner join teach on student.sid = teach.sid where tid = #{tid}") List<Student> getStudentByTid (int tid) ;
需要修改配置文件中的映射器注册:
1 2 3 4 <mappers > <mapper class ="com.test.mapper.MyMapper" /> </mappers >
还可以使用注解编写SQL语句,使用XML实现映射规则。
1 2 3 @ResultMap("test") @Select("select * from student") List<Student> getAllStudent () ;
使用@ConstructorArgs注解来指定构造方法,当出现两个以上参数时,需要使用@Param来指定参数名称:
1 2 3 4 5 6 @ConstructorArgs({ @Arg(column = "sid", javaType = int.class), @Arg(column = "name", javaType = String.class) }) @Select("select * from student where sid = #{sid} and sex = #{sex}") Student getStudentBySidAndSex (@Param("sid") int sid, @Param("sex") String sex) ;
若两个参数一个是基本类型一个是对象类型,可以通过参数名称.属性的方式:
1 2 @Insert("insert into student(sid, name, sex) values(#{sid}, #{student.name}, #{student.sex})") int addStudent (@Param("sid") int sid, @Param("student") Student student) ;
使用注解控制缓存:
1 2 3 4 5 6 @CacheNamespace(readWrite = false) public interface MyMapper { @Select("select * from student") @Options(useCache = false) List<Student> getAllStudent () ;
动态代理 使用反射框架实现的动态代理。