trc20离线签名的demo,有trxdent-java,就不需要这个了,使用http的方式可以看看
打包依赖
这里选择main
分支
进入trident-java文件夹,可以看到有build.gradle,如果是mac系统,修改一下依赖
1 | protoc { |
IDEA打包
打包之后,在每个子模块的build/libs/xxx-jar
下面的操作就可以跟着开发文档了,创建项目
pom.xml
1 | <dependency> |
查询余额的代码
1 | public static String privateKey = "私钥"; |
转账示例
1 | public static String transferTrc(String fromAddress, String toAddress, String amount, String contractAddress, String privateKey) { |
相关推荐

2022-11-19
Flink
Flink尝试flink本地安装步骤一:下载 为了能够运行Flink,唯一的要求是安装有效的Java 8或11。您可以通过发出以下命令来检查Java的正确安装: 123456# 要安装java环境java -version# 下载解压flinktar -xzf flink-1.11.2-bin-scala_2.11.tgzcd flink-1.11.2-bin-scala_2.11 步骤二:启动本地集群 1234$ ./bin/start-cluster.shStarting cluster.Starting standalonesession daemon on host.Starting taskexecutor daemon on host. 步骤三:提交一个job 12345678$ ./bin/flink run examples/streaming/WordCount.jar$ tail log/flink-*-taskexecutor-*.out (to,1) (be,1) (or,1) (not,1) (to,2) (be,2) 步骤四:停止集群 1$ ./bin/stop-cluster.sh 使用DataStream API进行欺诈检测Java环境123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>frauddetection</groupId> <artifactId>frauddetection</artifactId> <version>0.1</version> <packaging>jar</packaging> <name>Flink Walkthrough DataStream Java</name> <url>https://flink.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <flink.version>1.10.2</flink.version> <java.version>1.8</java.version> <scala.binary.version>2.11</scala.binary.version> <maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.target>${java.version}</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-walkthrough-common_${scala.binary.version}</artifactId> <version>${flink.version}</version> </dependency> <!-- This dependency is provided, because it should not be packaged into the JAR file. --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-streaming-java_${scala.binary.version}</artifactId> <version>${flink.version}</version><!-- <scope>provided</scope>--> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.0</version> <scope>provided</scope> </dependency> <!-- Add connector dependencies here. They must be in the default scope (compile). --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-kafka-0.10_${scala.binary.version}</artifactId> <version>${flink.version}</version> </dependency> <!-- Add logging framework, to produce console output when running in the IDE. --> <!-- These dependencies are excluded from the application JAR by default. --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.7</version> <scope>runtime</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <!-- Java Compiler --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <!-- We use the maven-shade plugin to create a fat jar that contains all necessary dependencies. --> <!-- Change the value of <mainClass>...</mainClass> if your program entry point changes. --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.0.0</version> <executions> <!-- Run shade goal on package phase --> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <artifactSet> <excludes> <exclude>org.apache.flink:force-shading</exclude> <exclude>com.google.code.findbugs:jsr305</exclude> <exclude>org.slf4j:*</exclude> <exclude>log4j:*</exclude> </excludes> </artifactSet> <filters> <filter> <!-- Do not copy the signatures in the META-INF folder. Otherwise, this might cause SecurityExceptions when using the JAR. --> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>spendreport.FraudDetectionJob</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> <pluginManagement> <plugins> <!-- This improves the out-of-the-box experience in Eclipse by resolving some warnings. --> <plugin> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <versionRange>[3.0.0,)</versionRange> <goals> <goal>shade</goal> </goals> </pluginExecutionFilter> <action> <ignore/> </action> </pluginExecution> <pluginExecution> <pluginExecutionFilter> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <versionRange>[3.1,)</versionRange> <goals> <goal>testCompile</goal> <goal>compile</goal> </goals> </pluginExecutionFilter> <action> <ignore/> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin> </plugins> </pluginManagement> </build></project> 输入File输入123456public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); final DataStream<String> stringDataStreamSource = env.readTextFile("Sensor.txt"); stringDataStreamSource.print("data"); env.execute();} Kafka需要引入包 12345<dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-kafka-0.10_${scala.binary.version}</artifactId> <version>${flink.version}</version></dependency> 12345678public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 配置kafka,账号密码 final Properties properties = new Properties(); final DataStreamSource<String> sensor = env.addSource(new FlinkKafkaConsumer010<String>("sensor", new SimpleStringSchema(), properties)); sensor.print("data"); env.execute();} 集合获取123456789101112131415public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream<SensorReading> dataStreamSource = env.fromCollection(Arrays.asList( new SensorReading("sen1", 1l, 37.5), new SensorReading("sen2", 2l, 38.5), new SensorReading("sen3", 3l, 39.5), new SensorReading("sen4", 4l, 40.5))); final DataStreamSource<Integer> integerDataStreamSource = env.fromElements(11, 12, 13, 14, 15); dataStreamSource.print("data"); integerDataStreamSource.print("my list"); env.execute();} 输出自定义数据源模拟从kafka中获取 123456789public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // kafka配置 final Properties properties = new Properties(); // 加入自定义的数据源 final DataStreamSource sensor = env.addSource(new MySensorce()); sensor.print("data"); env.execute();} 自定义数据源 123456789101112131415161718192021222324252627282930313233public class MySensorce implements SourceFunction<SensorReading> { private boolean running = true; @Override public void run(SourceContext<SensorReading> sourceContext) throws Exception { final Random random = new Random(); final HashMap<String, Double> stringDoubleHashMap = new HashMap<>(); for (int i = 1; i < 11; i++) { stringDoubleHashMap.put(i + "", random.nextGaussian()); } while (running) { for (String id : stringDoubleHashMap.keySet()) { final double v = stringDoubleHashMap.get(id) + random.nextGaussian(); final SensorReading sensorReading = new SensorReading(id, System.currentTimeMillis(), v); sourceContext.collect(sensorReading); } Thread.sleep(1000); } } @Override public void cancel() { running = false; }} 算子基本算子map123456final SingleOutputStreamOperator<Integer> map = inputStream.map(new MapFunction<String, Integer>() { @Override public Integer map(String s) throws Exception { return s.length(); }}); flatmap123456final SingleOutputStreamOperator<String> stringSingleOutputStreamOperator = inputStream.flatMap(new FlatMapFunction<String, String>() { @Override public void flatMap(String s, Collector<String> collector) throws Exception { Arrays.stream(s.split(",")).forEach(collector::collect); }}); filter123456final SingleOutputStreamOperator<String> filter = inputStream.filter(new FilterFunction<String>() { @Override public boolean filter(String s) throws Exception { return s.startsWith("sen"); }}); 分流,合流1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); final DataStream<String> inputStream = env.readTextFile("Sensor.txt"); final DataStream<SensorReading> map = inputStream.map(line -> { final String[] split = line.split(","); return new SensorReading(split[0], Long.parseLong(split[1]), Double.parseDouble(split[2])); }); // 分流 final SplitStream<SensorReading> split = map.split(new OutputSelector<SensorReading>() { @Override public Iterable<String> select(SensorReading value) { return value.getTemp() > 30 ? Collections.singletonList("hight") : Collections.singletonList("low"); } }); final DataStream<SensorReading> hight = split.select("hight"); final DataStream<SensorReading> low = split.select("low"); final DataStream<SensorReading> all = split.select("low","hight"); hight.print("hight"); low.print("low"); all.print("all"); final SingleOutputStreamOperator<Tuple2<String, Double>> warningStream = hight.map(new MapFunction<SensorReading, Tuple2<String, Double>>() { @Override public Tuple2<String, Double> map(SensorReading sensorReading) throws Exception { return new Tuple2<>(sensorReading.getId(), sensorReading.getTemp()); } }); final ConnectedStreams<Tuple2<String, Double>, SensorReading> connectedStreams = warningStream.connect(low); // 合流 final SingleOutputStreamOperator<Object> result = connectedStreams.map(new CoMapFunction<Tuple2<String, Double>, SensorReading, Object>() { @Override public Object map1(Tuple2<String, Double> value) throws Exception { return new Tuple3<>(value.f0, value.f1, "高温报警"); } @Override public Object map2(SensorReading value) throws Exception { return new Tuple2<>(value.getId(), "正常"); } }); // 第二种方法,ubion final DataStream<SensorReading> union = hight.union(low); result.print(); union.print("ubion"); env.execute();} 富函数1234567891011121314151617181920212223242526272829303132333435363738394041424344public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); final DataStream<String> inputStream = env.readTextFile("Sensor.txt"); final DataStream<SensorReading> map = inputStream.map(line -> { final String[] split = line.split(","); return new SensorReading(split[0], Long.parseLong(split[1]), Double.parseDouble(split[2])); }); DataStream<Tuple2<String, Integer>> resultStream = map.map(new RichMyMapper()); resultStream.print(); env.execute();}public static class MyMapper implements MapFunction<SensorReading, Tuple2<String, Integer>> { @Override public Tuple2<String, Integer> map(SensorReading sensorReading) throws Exception { return new Tuple2<>(sensorReading.getId(), sensorReading.getId().length()); }}// 自定义富函数public static class RichMyMapper extends RichMapFunction<SensorReading, Tuple2<String, Integer>> { @Override public Tuple2<String, Integer> map(SensorReading value) throws Exception { // getRuntimeContext().getState(); return new Tuple2<>(value.getId(), getRuntimeContext().getIndexOfThisSubtask()); } @Override public void open(Configuration parameters) throws Exception { // 用来建立数据库连接 super.open(parameters); } @Override public void close() throws Exception { // 关闭数据库 super.close(); }} 分组,聚合1234567891011121314151617181920212223242526public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); final DataStream<String> inputStream = env.readTextFile("Sensor.txt"); final DataStream<SensorReading> map = inputStream.map(line -> { final String[] split = line.split(","); return new SensorReading(split[0], Long.parseLong(split[1]), Double.parseDouble(split[2])); }); // 分组 // final KeyedStream<SensorReading, Tuple> id = map.keyBy("id"); final KeyedStream<SensorReading, String> keyedStream = map.keyBy(SensorReading::getId); // reduce final SingleOutputStreamOperator<SensorReading> reduce = keyedStream.reduce(new ReduceFunction<SensorReading>() { @Override public SensorReading reduce(SensorReading valu1, SensorReading valu2) throws Exception { return new SensorReading(valu1.getId(), valu2.getTimestamp(), Math.max(valu1.getTemp(), valu2.getTemp())); } }); reduce.print(); env.execute();}

2022-11-19
Java
基础知识点循环标签 如果不在嵌套循环中使用,用了跳转,也就相当于continue一样,没有区别 在嵌套循环中使用才有效果,用了innerLoop之后,可以看到 j=2 的时候是不打印 j=3,程序不会到j=3的时候,因为j=2的时候就已经跳出去了 12345678910111213141516171819202122232425262728293031323334/** * 0====0 * 0====1 * 1====0 * 1====1 * 2====0 * 2====1 * ------------------- * 0====0 * 0====1 * 0====2 * 0====3 * 1====0 * 1====1 * 1====2 * 1====3 * 2====0 * 2====1 * 2====2 * 2====3 */public static void main(String[] args) { innerLoop: for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { if (j == 2) { continue innerLoop; } System.out.println(i+"===="+j); } }} Java常用的API方法集合/列表/数组/Map数组拼接1234567891011121314// 把数组风格,再循环添加逗号String[] split = result.split(" ");StringJoiner stringJoiner = new StringJoiner(",");Arrays.stream(split).forEach(stringJoiner::add);// 字符串数组数字,转成int类型,再转成数组Arrays.stream(split).mapToInt(Integer::parseInt).toArray()// BigDecimal想加,然后如果为空,就给个0BigDecimal winAmount = list.stream() .map(UserBetOrderRecord::getWinAmount) .reduce(BigDecimal::add) .map(bigDecimal -> bigDecimal.multiply(new BigDecimal("-1"))) .orElse(BigDecimal.ZERO); 数组转集合12345678910111213141516171819public static void test() { int[] arr = new int[]{4, 3, 2, 1, 12, 21, 11}; // 这样转是有问题的,不是想要的 // java.util.array List<int[]> list = Arrays.asList(arr); System.out.println(list); // 使用Hutool的方法,也不死想要的 // cn.hutool.core.collection List<int[]> ints = CollectionUtil.newArrayList(arr); System.out.println(ints); // 使用Spring的工具包,需要强转,强迫症受不了这种方法 // org.springframework.util.CollectionUtils List<Integer> objects = (List<Integer>) CollectionUtils.arrayToList(arr); System.out.println(objects);} 打印数组/集合/Map12345678910111213141516171819private static void test() { int[] arr = {1, 2, 3, 4, 5}; System.out.println(arr); // [I@17211155 System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5] List<Integer> integers = new ArrayList<>(); integers.add(1); integers.add(2); integers.add(3); System.out.println(integers); // [1, 2, 3] HashMap<Integer, String> integerStringHashMap = new HashMap<>(); integerStringHashMap.put(1, "a"); integerStringHashMap.put(2, "b"); integerStringHashMap.put(3, "c"); System.out.println(integerStringHashMap); // {1=a, 2=b, 3=c}} 数组截取1234567// java.util.Arrays// 从0开始,取出6个数private static void test() { int[] arr = {1, 2, 3, 4, 5, 6}; int[] ints = Arrays.copyOfRange(arr, 0, 6); System.out.println(Arrays.toString(ints));} 格式化数值格式化1234567private static void test() { String format = String.format("%02d", 2); System.out.println(format); // 02 String format2 = String.format("%02d", 20); System.out.println(format2); // 20} 数学余数/取模1234567891011121314private static void test() { int a = 5; int b = 3; // 使用 % 运算符取余数 int remainder = a % b; System.out.println(remainder); // 2 // 使用 Math.floorMod() 方法取余数 int floorMod = Math.floorMod(a, b); System.out.println(floorMod); // 2} BigDecimal比较1234567891011121314151617181920212223242526272829public static void main(String[] args) { BigDecimal a = new BigDecimal(10); BigDecimal b = new BigDecimal(5); int i = a.compareTo(b); switch (i) { case -1: // 小于 System.out.println("a < b"); break; case 0: // 等于 System.out.println("a = b"); break; case 1: // 大于 System.out.println("a > b"); break; } if (a.compareTo(b) != 0) { System.out.println("a != b"); } if (a.compareTo(b) != -1){ System.out.println("a >= b"); } if (a.compareTo(b) != 1) { System.out.println("a <= b"); } } 时间类型比较123456789101112131415161718192021222324252627public static void main(String[] args) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date begin = sdf.parse("2020-01-01 00:01:00"); Date end = sdf.parse("2020-01-01 00:02:00"); // 使用compareTo方法 int i = begin.compareTo(end); switch (i) { case -1: System.out.println("开始时间大于结束时间"); break; case 0: System.out.println("开始时间等于结束时间"); break; case 1: System.out.println("开始时间小于结束时间"); break; } // 使用before和after方法 boolean b = begin.before(end); System.out.println(b); boolean c = begin.after(end); System.out.println(c); } 值传递和引用传递123456789101112131415161718192021222324252627public class Demo { public static void main(String[] args) { HashMap<Integer, User> ingUserHashMap = new HashMap<Integer, User>(); User user = new User(); user.setId(1); ingUserHashMap.put(1, user); // {1=User(id=1, bigDecimal=10)} System.out.println(ingUserHashMap); // 从map里取值,然后再修改 User user1 = ingUserHashMap.get(1); user1.setBigDecimal(BigDecimal.ONE); // 打印map是修改了的 // {1=User(id=1, bigDecimal=1)} System.out.println(ingUserHashMap); }}@Data@ToStringclass User{ int id; BigDecimal bigDecimal = BigDecimal.TEN;} Lambda给集合分组 1234567891011121314151617181920212223242526public class Demo { public static void main(String[] args) { ArrayList<User> list = new ArrayList<>(); list.add(new User(1, "anthony")); list.add(new User(2, "anthony")); list.add(new User(1, "didid")); // 按照用户Id分组 Map<Integer, List<User>> collect = list.stream().collect(Collectors.groupingBy(User::getId)); // {1=[User(id=1, name=anthony), User(id=1, name=didid)], 2=[User(id=2, name=anthony)]} System.out.println(collect); // 1==[User(id=1, name=anthony), User(id=1, name=didid)] // 2==[User(id=2, name=anthony)] collect.forEach((id,userList)->{ System.out.println(id+"=="+userList); }); }}@AllArgsConstructor@Dataclass User{ Integer id; String name;} ThreadLocal掘金 HashMap面试八股文…. HashMap和HashTable的区别 HashMap HashTable null值 允许key,value为null,但最多允许一条记录的key为null 不允许有null 安全性 不安全,Collections.synchronizedMap方法,使HashMap具有线程安全的能力 线程安全的 遍历 Iterator 进行遍历 使用 Enumeration 进行遍历 负载因子 负载因子和扩容机制有关,意思是如果当前容器的容量,达到我们设置的定最大值,就要开始扩容 比如说当前的容器容量是16,负载因子是0.75,16*0.75=12,也就是说,当容量达到了12的时候就会进行扩容操作。 负载因子是1的时候: 只有当数组的16个一直都填满了,才回扩容,因此在填满的过程中,Hash冲突是避免不了的,也就意味着会出现大量的Hash冲突,底层的红黑树就变得复杂,这种就牺牲了时间,保证空间的利用率 负载因为是0.5的时候: 当到达数组一半的就开始扩容,既然填充的元素少了,Hash冲突就变少,那么底层的链表长度和红黑数也就变少,查询效率就高, 原本存储1M的数据,现在需要2M,时间效率提升了,但是空间利用率降低了 不同JDK版本HashMap的区别 组成 数组+链表 数组+链表+红黑树 hash 值我们能够快速定位到数组的具体下标,但是之后的话, 需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度 当满足一定条件时,会将链表转换为红黑树 **链表转红黑树要满足的条件 ** 链表中的元素个数>=8,为什么? 和hashcode碰撞次数的泊松分布有关,主要是为了寻找一种时间和空间的平衡 红黑数的TreeNode 是 链表中的Node所占空间的2倍,虽然红黑树的查询效率要高于链表,但是,当链表长度比较小的时候,即使全部遍历,时间复杂度也不会太高 为什么是8,JDK源码也没有说,只是说做了很多时间,经验的问题 红黑树转链表的阀值是6,因为:避免红黑树和链表频繁转换 当前数组的长度>=64,为什么? 链表转为红黑树的目的是为了解决链表过长,导致查询和插入的效率慢的问题 如果要解决这个问题,也可以通过数组扩容,把链接缩短也可以解决问题 所以在数组长度还不太长的情况,可以先通过数组扩容来解决链表过长的问题 满足这两个条件才会变成红黑树 HashMap的数组的大小是2的幂 TODO 还是没有搞明白 只有数组长度是2的幂次方倍才能够确保数组中的每一个位置发生hash冲突的概率是相同的,数组长度减一的二进制码必须全部是1,否则会出现部分位置永远不会发生hash冲突而造成资源浪费 HashMap put流程 对key进行hash算法,得到一个hashcode 如果数组长度为0或者为null,对数组进行扩容,得到数组长度n 通过 key的hashcode&数组长度n 计算出数组下标 如果数组下标位置中没有数据,将put进来的数据封装Node对象并存到给下标位置 如果数组下标位置有数据p,即发生hash碰撞,如果key存在,覆盖数据 如果数据p属于红黑树,会把新数据插入到红黑树中 如果以上都不是就遍历链表,遍历链表过程中统计链表长度,当链表长度超过8 进行treeifyBin 红黑树化链表 treeifyBin树化中如果数组长度小于64或数组为null则进行扩容,否则数组长度大于等于64 链表转红黑树 HashMap 是如何扩容的? HashMap的扩容指的就是数组的扩容, 因为数组占用的是连续内存空间,所以数组的扩容其实只能新开一个新的数组,然后把老数组上的元素转移到新数组上来,这样才是数组的扩容 先新建一个2被数组大小的数组 然后遍历老数组上的每一个位置,如果这个位置上是一个链表,就把这个链表上的元素转移到新数组上去 在jdk8中,因为涉及到红黑树,这个其实比较复杂,jdk8中其实还会用到一个双向链表来维护红黑树中的元素,所以jdk8中在转移某个位置上的元素时,会去判断如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置,一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用拆分,否则判断这两个子链表的长度,如果超过8,则转成红黑树放到对应的位置,否则把单向链表放到对应的位置。 元素转移完了之后,在把新数组对象赋值给HashMap的table属性,老数组会被回收到。 多线程线程池手写一个线程池1:编写任务类(MyTask),实现Runnable接口; 12345678910111213141516171819202122232425public class MyTask implements Runnable { private int id; public MyTask(int id) { this.id = id; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "即将开始任务:" + id); try { Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("线程:"+name + "完成了任务:" + id); } @Override public String toString() { return "MyTask{" + "id=" + id + '}'; }} 2:编写线程类(MyWorker),用于执行任务,需要持有所有任务; 123456789101112131415161718public class MyWorker extends Thread{ private List<Runnable> tasks; public MyWorker(String name, List<Runnable> tasks) { super.setName(name); this.tasks = tasks; } @Override public void run() { // 判断集合中是否有任务,如果有就一直运行 while (!tasks.isEmpty()) { Runnable remove = tasks.remove(0); remove.run(); } }} 3:编写线程池类(MyThreadPool),包含提交任务,执行任务的能力; 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647public class MyThreadPool { // 任务队列 private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>()); // 当前线程数量 private int num; // 核心线程数量 private int corePoolSize; // 最大线程数量 private int maxSize; // 任务队列的长度 private int workSize; public MyThreadPool(int corePoolSize, int maxSize, int workSize) { this.corePoolSize = corePoolSize; this.maxSize = maxSize; this.workSize = workSize; } // 提交方法-将任务添加到队列中 public void submit(Runnable runnable){ // 判断当前队列的数量,是否超出了最大任务数量 if (tasks.size() >= workSize) { System.out.println("任务"+runnable+"被丢弃了"); }else { tasks.add(runnable); // 执行方法-判断当前线程的数量,决定创建核心线程数量还是非线程数量 execTask(runnable); } } private void execTask(Runnable runnable) { // 判断当前线程中的线程总数量,是否超出了核心线程数 if (num < corePoolSize) { // 创建核心线程 new MyWorker("核心线程:"+num, tasks).start(); num++; } else if (num < maxSize) { // 创建非核心线程 new MyWorker("非核心线程:"+num, tasks).start(); num++; }else { System.out.println("任务:"+runnable+"被缓存了...."); } }} 4:编写测试类(MyTest),创建线程池对象,提交多个任务测试; 123456789public class MyTest { public static void main(String[] args) { MyThreadPool myThreadPool = new MyThreadPool(2, 4, 20); for (int i = 0; i < 10; i++) { MyTask myTask = new MyTask(i); myThreadPool.submit(myTask); } }} 内置线程池ThreadPoolExecutor的使用 12345678public ThreadPoolExecutor(int corePoolSize, //核心线程数量 int maximumPoolSize,// 最大线程数 long keepAliveTime, // 最大空闲时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 饱和处理机制) corePoolSize - 线程池核心线程的数量,即使线程是空闲的,核心线程也不会被销毁,除非设置了 allowCoreThreadTimeOut 为 true。 maximumPoolSize - 线程池中允许的最大线程数。 keepAliveTime - 非核心线程空闲时的超时时间,超过这个时间就会被销毁。 unit - keepAliveTime 参数的时间单位。 workQueue - 用于保存等待执行任务的阻塞队列。 SynchronousQueue: 一个不存储元素的阻塞队列。每个插入操作必须等待另一个线程的对应移除操作,反之亦然。适合于传递性的请求。 LinkedBlockingQueue: 一个无界队列,使用链表实现。如果没有指定容量,默认将是 Integer.MAX_VALUE。当任务数量超过核心线程数量时,多余的任务将被放置在队列中等待执行。 ArrayBlockingQueue: 一个有界的阻塞队列,使用数组实现。在创建时需要指定容量大小。适合于有界任务队列的场景,如数据库连接池和消息队列等。 threadFactory - 用于创建新线程的工厂。 handler - 当线程池中的线程数达到最大值且阻塞队列已满时,用来处理新提交的任务的饱和策略。 AbortPolicy: 默认的策略,将抛出RejectedExecutionException。 CallerRunsPolicy: 当任务被拒绝添加时,会使用当前线程执行被拒绝的任务。 DiscardPolicy: 直接丢弃被拒绝的任务,不提供任何反馈。 DiscardOldestPolicy: 丢弃等待时间最长的任务,然后尝试提交新的任务。 ExecutorService接口的常用方法: 接口名 作用 void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。 List shutdownNow() 停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。 Future submit(Callable task) 执行带返回值的任务,返回一个Future对象。 Future<?> submit(Runnable task) 执行 Runnable 任务,并返回一个表示该任务的 Future。 Future submit(Runnable task, T result) 执行 Runnable 任务,并返回一个表示该任务的 Future。 ExecutorService接口的的实现类 方法 static ExecutorService newCachedThreadPool() 创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建 static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) 线程池中的所有线程都使用ThreadFactory来创建,这样的线程无需手动启动,自动执行; static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池 static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建。 static ExecutorService newSingleThreadExecutor() 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。 static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) 创建一个使用单个 worker 线程的 Executor,且线程池中的所有线程都使用ThreadFactory来创建。 ScheduledExecutorService任务调度ScheduledExecutorService是ExecutorService的子接口,具备了延迟运行或定期执行任务的能力 方法() static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务; static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建,且允许延迟运行或定期执行任务; static ScheduledExecutorService newSingleThreadScheduledExecutor() 创建一个单线程执行程序,它允许在给定延迟后运行命令或者定期地执行。 static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。 ScheduledExecutorService常用方法如下: 方法 ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) 延迟时间单位是unit,数量是delay的时间后执行callable。 ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) 延迟时间单位是unit,数量是delay的时间后执行command。 ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 延迟时间单位是unit,数量是initialDelay的时间后,每间隔period时间重复执行一次command。 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) 创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。 Future异步计算结果 boolean cancel(boolean mayInterruptIfRunning) 试图取消对此任务的执行。 V get()如有必要,等待计算完成,然后获取其结果。 V get(long timeout, TimeUnit unit) 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。 boolean isCancelled() 如果在任务正常完成前将其取消,则返回 true。 boolean isDone() 如果任务已完成,则返回 true。 代码演示ExecutorService12345678910111213141516171819202122232425262728293031323334353637383940414243// 演示 newCachedThreadPoolpublic class Demo { public static void main(String[] args) {// test1();// test2(); } private static void test1() { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable(i)); } } private static void test2() { ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable(i)); } }}class MyRunable implements Runnable { int i = 0; public MyRunable(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445// 演示newFixedThreadPoolpublic class Demo2 { public static void main(String[] args) {// test1(); test2(); } private static void test1() { ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable2(i)); } } private static void test2() { ExecutorService executorService = Executors.newFixedThreadPool(3,new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable2(i)); } }}class MyRunable2 implements Runnable { int i = 0; public MyRunable2(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546// 演示newSingleThreadExecutorpublic class Demo3 { public static void main(String[] args) {// test1(); test2(); } private static void test1() { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable3(i)); } } private static void test2() { ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable3(i)); } }}class MyRunable3 implements Runnable { int i = 0; public MyRunable3(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465// ExecutorService接口的shutdown(),shutdownNow(),submit()public class Demo4 { public static void main(String[] args) {// test1(); test2(); } private static void test2() { ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable4(i)); } // 立刻关闭线程池,如果线程池中的还有缓存任务,没有执行,则取消执行,并返回这些任务 List<Runnable> runnables = executorService.shutdownNow(); System.out.println(runnables); // 不能再接收新的方法了,会报错 executorService.submit(new MyRunable4(888)); } private static void test1() { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable4(i)); } // 关闭线程池,仅仅是不再接收新的任务,以前的任务还会记录执行 executorService.shutdown(); // 不能再接收新的方法了,会报错 executorService.submit(new MyRunable4(888)); }}class MyRunable4 implements Runnable { int i = 0; public MyRunable4(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); } @Override public String toString() { return "MyRunable4{" + "i=" + i + '}'; }} 代码演示ScheduledExecutorService12345678910111213141516171819202122232425262728293031323334/** * 演示newScheduledThreadPool,测试延迟执行和重复执行的功能 */public class Demo { public static void main(String[] args) { // 获取具备延迟执行任务的线程池对象 ScheduledExecutorService es = Executors.newScheduledThreadPool(3); for (int i = 0; i < 10; i++) { // 创建多个任务,并且提交任务,每个任务延迟2s执行 es.schedule(new MyRunable(i), 2, TimeUnit.SECONDS); } System.out.println("结束"); }}class MyRunable implements Runnable { int i = 0; public MyRunable(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243// 演示newScheduledThreadPoolpublic class Demo2 { public static void main(String[] args) { // 获取具备延迟执行任务的线程池对象 ScheduledExecutorService es = Executors.newScheduledThreadPool(3, new ThreadFactory() { int n = 1; @Override public Thread newThread(Runnable r) { return new Thread("自定义线程:" + n++); } }); // 创建多个任务,并且提交任务,每个任务延迟2s执行 es.scheduleAtFixedRate(new MyRunable2(1), 1,2, TimeUnit.SECONDS); System.out.println("结束"); }}class MyRunable2 implements Runnable { int i = 0; public MyRunable2(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); try { Thread.sleep(1500); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("线程:"+name + "执行了任务:"+i); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243// 演示newSingleThreadScheduledExecutorpublic class Demo3 { public static void main(String[] args) { // 获取具备延迟执行任务的线程池对象 ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor( new ThreadFactory() { int n = 1; @Override public Thread newThread(Runnable r) { return new Thread("自定义线程:" + n++); } }); // 创建多个任务,并且提交任务,每个任务延迟2s执行 es.scheduleWithFixedDelay(new MyRunable2(1), 1,2, TimeUnit.SECONDS); System.out.println("结束"); }}class MyRunable3 implements Runnable { int i = 0; public MyRunable3(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("线程:"+name + "执行了任务:"+i); }} 代码演示Future1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556// public class Demo5 { public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException { // 创建线程池对象 ExecutorService executorService = Executors.newCachedThreadPool(); Future<Integer> submit = executorService.submit(new MyCall(1, 2));// test1(submit);// test2(submit); } private static void test2(Future<Integer> submit) throws InterruptedException, ExecutionException, TimeoutException { Thread.sleep(1000); System.out.println("取消任务执行的结果" + submit.cancel(true)); // 主线程等待超时 Integer i = submit.get(1, TimeUnit.SECONDS); System.out.println("任务执行的结果:" + i); } private static void test1(Future<Integer> submit) throws InterruptedException, ExecutionException { // 判断任务是否完成 boolean done = submit.isDone(); System.out.println("第一次判断任务是否完成:" + done); boolean cancelled = submit.isCancelled(); System.out.println("第一次判断任务是否取消:" + cancelled); // 无限期等待 Integer i = submit.get(); System.out.println("任务执行的结果:" + i); System.out.println("第二 次判断任务是否完成:" + submit.isDone()); System.out.println("第一次判断任务是否取消:" + submit.isCancelled()); }}class MyCall implements Callable<Integer> { int a; int b; public MyCall(int a, int b) { this.a = a; this.b = b; } @Override public Integer call() throws Exception { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "准备开始计算:"); Thread.sleep(2000); System.out.println("线程:"+name + "计算完成:"); return a+b; }} JVMJVM内存结构 程序计数器虚拟机指令的执行流程:CPU不是不是直接读二进制字节码 , 解释器读二进制字节码 转换成 机器码,交给CPU执行 程序计数器的在作用:记住下一跳JVM指令的执行地址 特点: 线程私有的, 不会存在内存溢出 虚拟机栈每个栈由多个栈桢组成,对应这每次方法调用的时所占用的内存 每个线程只能有一个活动栈桢,对应着当前正在执行的那个方法 数据结构,像子弹夹,先进后出,所以不需要GC -Xss 设置每个线程栈内存大小,默认1M,内存越大,能使用的线程越少 演示栈桢 演示线程安全方法内的局部变量线程是安全的 12345678910public class Demo3 { public void test() { // 每个线程,有各自的x int x = 0; for (int i = 0; i < 1000; i++) { x++; } System.out.println(x); }} 演示线程不安全如果是静态变量,就不是安全的了 123456789101112public class Demo3 { static int x = 0; // 多线程运行 public void demo() { for (int i = 0; i < 1000; i++) { x++; } System.out.println(x); }} 演示栈内存溢出栈桢过大或者过多会溢出 12345678910111213141516171819202122232425public class Demo2 { private static int count = 0; public static void main(String[] args) { try { method(); } catch (Throwable e) { e.printStackTrace(); // 19994 System.out.println(count); } } private static void method(){ count++; method(); }}// java.lang.StackOverflowError// at 栈.Demo2.method(Demo2.java:22)// at 栈.Demo2.method(Demo2.java:22) 线程诊断1.线程CPU占用高 12345678# 使用top命令看哪个进程占用CPU过高,只能看到进程,不能看到线程# 查看哪个线程占用过高ps H -eo pid,tid,%cpu | grep 进程id# jstack 线程id在具体查找的时候,十进制的线程ID转成16进制 2.运行很长时间,得不到结果 有可能发生死锁了 本地方法栈native 方法,比如object里的clone方法 堆使用-Xmx 设置堆内存空间大小 特点: 线程共享的,堆中对象需要考虑线程安全的问题 有垃圾回收机制 演示堆内存溢出1234567891011121314151617public class Demo { public static void main(String[] args) { int i = 0; List<String> List = new ArrayList<>(); String a = "hello"; try { while (true) { List.add(a); a = a + a; i++; } } catch (Exception e) { e.printStackTrace(); System.out.println(i); } }} 演示jmap12345678# 查看堆内存使用空间# 16279是pidjmap -heap 16279Error: -heap option usedCannot connect to core dump or remote debug server. Use jhsdb jmap instead# 对于jdk8之后的版本,不能再使用jmap -heap pid的命令了jhsdb jmap --heap --pid 16279 演示jconsole12345678910111213public class Demo2 { public static void main(String[] args) throws InterruptedException { System.out.println("1....."); Thread.sleep(30000); byte[] bytes = new byte[1028 * 1024 * 10]; System.out.println("2....."); Thread.sleep(30000); bytes = null; System.gc(); System.out.println("3....."); Thread.sleep(1000000); }} 打印1的时候,是刚开始运行,默认的内存占用 打印2的时候,代码创建了一个10MB数组 打印3的时候,数据被垃圾回收了,还回收了一些默认的创建的空间,所以内存占用就降下来了 演示jvisualvm点击gc之后,内存占用不下降的情况 1234567891011121314151617181920/** * 演示jvisualvm */public class Demo3 { public static void main(String[] args) throws InterruptedException { ArrayList<Persion> persions = new ArrayList<>(); for (int i = 0; i < 200; i++) { persions.add(new Persion()); } Thread.sleep(99999999999999999L); }}class Persion{ private byte[] big = new byte[1024 * 1024];} 点击Heap Dump 仔细找找,就能看到那些数据占用的内存比较多 方法区 12345678演示永久代内存溢出:# 1.8以前会导致永久代内存溢出java.lang.OutOfMemoreryError: PermGen space-XX:MaxPermSize=8m# 1.8以前会导致元空间内存溢出java.lang.OutOfMemoreryError: Metaspace-XX:MaxMetaspaceSize=8m 123456789101112131415161718192021222324252627282930313233343536// 没有演示出来import jdk.internal.org.objectweb.asm.ClassWriter;import jdk.internal.org.objectweb.asm.Opcodes;/** * 演示元空间内存溢出 * 方法区内存溢出,1.8才有这个版本 * -XX:MaxMetaspaceSize=8m * * ClassLoader:类加载器,可以用来加载类的二进制字节码 */public class Demo extends ClassLoader{ public static void main(String[] args) { int j = 0; try { Demo test = new Demo(); for (int i = 0; i < 10000; i++, j++) { // 生成类的二进制字节码 ClassWriter cw = new ClassWriter(0); // 版本号 // 访问修饰符 // 类名 // 包名 // 父类 // 接口名称,这里没有实现接口 cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null); // 返回byte数组 byte[] code = cw.toByteArray(); // 加载类,生成Class对象 test.defineClass("Class" + i, code, 0, code.length); } }finally { System.out.println(j); } }} 运用的场景: Spring和Mybatis都使用到了CGLIB技术,CGLIB也会像上面演示的使用ClassWriter创建对象,创建的对象多了,就会导致方法区异常,所以就移动到直接内存,虽然没啥本质上的作用,只是会影响OOM的时间 常量池123456789101112/** * 常量池 * 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令) */public class Demo3 { public static void main(String[] args) { System.out.println("Hello world"); }}// 使用命令编译javap -v Demo3.class 编译的结果 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283// 以下是类的基本信息Classfile /Users/anthony/Documents/GitHub/study-java/java-base/target/classes/方法区/Demo3.class Last modified 2023年8月23日; size 538 bytes MD5 checksum 1aca08c8871c6946b2737975bbf15625 Compiled from "Demo3.java"public class 方法区.Demo3 minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #5 // 方法区/Demo3 super_class: #6 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 1 // 常量池Constant pool: #1 = Methodref #6.#20 // java/lang/Object."<init>":()V #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #23 // Hello world #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #26 // 方法区/Demo3 #6 = Class #27 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 L方法区/Demo3; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 SourceFile #19 = Utf8 Demo3.java #20 = NameAndType #7:#8 // "<init>":()V #21 = Class #28 // java/lang/System #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream; #23 = Utf8 Hello world #24 = Class #31 // java/io/PrintStream #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V #26 = Utf8 方法区/Demo3 #27 = Utf8 java/lang/Object #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; #31 = Utf8 java/io/PrintStream #32 = Utf8 println #33 = Utf8 (Ljava/lang/String;)V // 类的方法定义{ public 方法区.Demo3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this L方法区/Demo3; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 // 虚拟机指令 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello world 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 8: 0 line 9: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String;}SourceFile: "Demo3.java" 串池(StringTable)的特性常量池中的字符串仅是符号,第一次用到的时候才回变成对象 利用串池的机制,来避免重复创建字符串对象 字符串变量拼接的原理是StringBuilder(1.8) 字符串常量拼接的原理是编译期优化 可以使用intern方法,主动将串池中的还没有的字符串对象放入串池 12345678910111213public class Demo2 { public static void main(String[] args) { String s = new String("a") + new String("b"); // 这是1.8的 讲字符串对象尝试放入到串池中,如果有不会放入,如果没有则会放入串池,会把串池中的对象返回 // 1.8和1.6 不一样 String intern = s.intern(); // true System.out.println(intern=="ab"); // true System.out.println(s=="ab"); }} 常量池和串池的区别1234567891011121314151617181920212223// StringTable["a","b","ab"]public class Demo { // 反编译之后,常量池中的信息,都会被加载到运行时常量池中,这时都是常量池中的符号,还没有变成Java字符串对象 // 运行到s1这行代码的时候,就会把a符号变成a字符串对象,存入到stringtable[]中 // 用到才会创建字符串对象放到串池中,也就是懒加载 public static void main(String[] args) { String s1 = "a"; // 懒加载 String s2 = "b"; String s3 = "ab"; // 字符串相加的原理:new StringBuilder().append(s1).append(s2).toString() // 上面s3的位置是在串池中,s4 是在堆内存中 String s4 = s1 + s2; // false System.out.println(s3 == s4); // javac在编译期间就确定为ab了,运行的时候直接去串池中查找 String s5 = "a" + "b"; // true System.out.println(s3 == s5); }} 证明字符串加载的延迟特性 StringTable的位置位置的移动,在1.6的时候要使用full gc才能被回收,1.8移动到堆内存了,minco gc 就可以回收 StringTable的调优1.调整-XX:StringTableSize=桶的个数 2.考虑将字符串对象是否入池 比如有很多人的地址,都包含北京市这样的字符串,就应该考虑入池 直接内存(Direct Memory)常见于NIO(ByteBuffer)操作时,用户数据缓冲区 分配回收成本较高,读写性能高 不受JVM内存回收管理 演示直接内存溢出12345678910111213141516171819202122232425262728public class Demo { static int _100MB = 1024 * 1024 * 100; public static void main(String[] args) { List<ByteBuffer> objects = new ArrayList<>(); int i = 0; try { while (true) { ByteBuffer allocate = ByteBuffer.allocateDirect(_100MB); objects.add(allocate); i++; } } finally { System.out.println(i); } }}Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory at java.base/java.nio.Bits.reserveMemory(Bits.java:175) at java.base/java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:118) at java.base/java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:317) at 直接内存.Demo.main(Demo.java:18) 释放原理使用UnSafe对象完成了直接内存的分配回收,而且回收需要主动调用freeMemory方法 ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,就会由ReferenceHandler线程通过Cleaner方法调用freeMemory来释放直接内存 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123public class Demo2 { static int _1GB = 1024 * 1024 * 1024; public static void main(String[] args) throws IOException { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1GB); System.out.println("分配完毕"); // 看任务管理器,可以看到这个程序占用1G+ System.in.read(); System.out.println("开始释放"); byteBuffer = null; System.gc(); // 可以看到被会回收了,为什么会被回收呢 System.in.read(); }}// 释放原理public class Demo3 { static int _1GB = 1024 * 1024 * 1024; public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException { Unsafe unsafe = getUnsafe(); // 分配内存 // base 表示内存地址 long base = unsafe.allocateMemory(_1GB); unsafe.setMemory(base,_1GB,(byte)0); System.in.read(); // 释放内存 unsafe.freeMemory(base); System.in.read(); } public static Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException { Field declaredField = Unsafe.class.getDeclaredField("theUnsafe"); declaredField.setAccessible(true); return (Unsafe) declaredField.get(null); }}// 源码ByteBuffer.allocateDirect(_1GB);public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity);}DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); long base = 0; try { // 分配内存 base = UNSAFE.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } UNSAFE.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } // 当this被回收时,就会调用回调函数Deallocator // 就是Java对象被回收,触发直接内存回收 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null;}// 继承了虚应用对象public class Cleaner extends PhantomReference<Object>{ public void clean() { if (!remove(this)) return; try { thunk.run(); } catch (final Throwable x) { AccessController.doPrivileged(new PrivilegedAction<>() { public Void run() { if (System.err != null) new Error("Cleaner terminated abnormally", x) .printStackTrace(); System.exit(1); return null; }}); } }}// 回调函数private static class Deallocator implements Runnable{ private long address; private long size; private int capacity; private Deallocator(long address, long size, int capacity) { assert (address != 0); this.address = address; this.size = size; this.capacity = capacity; } public void run() { if (address == 0) { // Paranoia return; } // 释放直接内存 UNSAFE.freeMemory(address); address = 0; Bits.unreserveMemory(size, capacity); }} 垃圾回收机制可达性分析法如何判断对象是否可以回收 引用计数法 可达性分析法,使用Eclipse Memory Analyzer ,沿着GC Root查找,找到就不能回收 Eclipse Memory Analyzer的使用方法 123456789101112131415161718192021/** * 使用mat 演示GC root */public class Demo { public static void main(String[] args) throws IOException { ArrayList<Object> list1 = new ArrayList<>(); list1.add("a"); list1.add("b"); System.out.println(1); // jsp 获取到进程id // jmap -dump:format=b,live,file=1.bin 进程id System.in.read(); list1 = null; System.out.println(2); System.in.read(); System.out.println("end...."); }} live 代表的意思是,执行dump文件的时候,先执行一次GC 大概有四类: System Class Native Stack,调用native方法要使用到的 Thread,活动线程,使用到的对象 Busy Monitor,各种锁 四种引用 强引用 强引用 软引用 有软引用引用该对象,只有在内存不足时才会被垃圾回收 可以配合引用队列来释放引用自身 弱引用 在GC时,不管内存是否充足都会被回收 可以配合引用队列来释放引用自身 需引用 主要分配ByteBuffer使用,被引用对象回收时,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存 必须配合引用队列 终结器引用 无需手动编码,GC时,终结器引用入队,再由Finalizer线程通过终结器引用并调用finalize方法,第二次GC时,才能被回收 垃圾回收算法 标记清除 标记整理 复制 标记清除原本的内存使用情况 标记 把要清楚的内存的开始地址放在列表你,等下次要分配内存的时候,直接在列表里找 优点:就是速度快 缺点:产生内存碎片 标记整理标记 整理 优点:不会产生碎片 缺点:速度慢 复制算法原来的内存使用 标记 复制 清理 交换from 和 to的位置 分代回收12345新生代: Minor GC 伊甸园 幸存区 from 幸存区 to老年代: Full GC 也会清理新生代 对象首先分配在伊甸园区域 新生代空间不足时,触发 minor ge,伊甸园和trom 存活的对象使用copy 复制到10中,存活的对象年龄加1并且交换 from to minor se 会引1发 stop the wvorid,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行 当对象寿命超过國值时,会晋升至老年代,最大寿命是15 (4bit) 当老年代空间不足,会先尝试触发 mninor ge,如果之后空间仍不足,那么触发 foll ge, sTw的时间更长 含义 参数 堆初始大小 Xms 堆最大大小 xmx 或 -Xx:MaxFeapSize=size 新生代大小 Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size) 幸存区比例(动态) Xx:InitialSurvivorRatio=ratio 和-XX:+UseAdaptiveSizePolicy 幸存区比例 XX:SurvivorRatio=ratio 晋升阈值 XX:MaxTenuringThreshold=threshold (threshold和具体的回收器有关) 晋升详情 XX: +PrintTenuringDistribution GC详情 XX:+PrintGCDetails - verbose:go FullGC 前 MinorGC XX:+ScavengeBeforeFullGC Servlet搭建环境(XML版本)下载,解压tomcat,运行 1234567891011121314151617181920212223# 进入目录cd apache-tomcat-10.0.27/bin# 运行sh startup.sh# 报错信息The file is absent or does not have execute permissionThis file is needed to run this program# 授权chmod 777 apache-tomcat-10.0.27# 再次运行sh startup.sh# 打印结果Using CATALINA_BASE: /Users/anthony/Downloads/apache-tomcat-10.0.27Using CATALINA_HOME: /Users/anthony/Downloads/apache-tomcat-10.0.27Using CATALINA_TMPDIR: /Users/anthony/Downloads/apache-tomcat-10.0.27/tempUsing JRE_HOME: /Library/Java/JavaVirtualMachines/jdk-18.0.1.jdk/Contents/HomeUsing CLASSPATH: /Users/anthony/Downloads/apache-tomcat-10.0.27/bin/bootstrap.jar:/Users/anthony/Downloads/apache-tomcat-10.0.27/bin/tomcat-juli.jarUsing CATALINA_OPTS:Tomcat started.# 访问http://localhost:8080 文件目录结构 导包 1234567891011121314151617181920212223<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>servlet-xml</artifactId> <!-- 打war包 --> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>servlet-xml Maven Webapp</name> <dependencies> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-servlet-api</artifactId> <version>10.0.4</version> </dependency> </dependencies> <build> <finalName>servlet-xml</finalName> </build></project> 因为是使用的tomcat10,所有导入的包不一样,如果是tomcat10之前的版本,导入的包好像是java-serverlt-api 参考:https://taurusxin.com/tomcat10-issue/ 代码 HelloWorld.java 1234567891011121314151617181920212223242526272829303132333435363738package com.mmzcg.servlet;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类public class HelloWorld extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + message + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("我就没见过销毁过"); }} Version.java 1234567891011121314151617181920212223242526272829303132333435363738package com.mmzcg.servlet;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类public class Version extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + "返回的是版本号" + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("我就没见过销毁过"); }} index.jsp 12345678910111213// 解决中文乱码<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><html><body><h2>这是首页</h2>有两个url<a href="/hello">hello</a><a href="/version">version</a></body></html> web.xml 1234567891011121314151617181920212223242526<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>HelloWorld</servlet-name> <servlet-class>com.mmzcg.servlet.HelloWorld</servlet-class> </servlet> <servlet> <servlet-name>Version</servlet-name> <servlet-class>com.mmzcg.servlet.Version</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorld</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Version</servlet-name> <url-pattern>/version</url-pattern> </servlet-mapping></web-app> 12345678910111213141516171819202122<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>servlet-xml</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>servlet-xml Maven Webapp</name> <dependencies> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-servlet-api</artifactId> <version>10.0.4</version> </dependency> </dependencies> <build> <finalName>servlet-xml</finalName> </build></project> 运行环境一:在IDEA上运行tomcat 添加tomcat模板 配置tomcat_home 配置包和路径 运行环境一:在命令行运行tomcat 先停掉tomcat,复制war包到webapps目录里 再启动tomcat,默认的访问地址就是:http://localhost:8080/servlet-xml/ 搭建环境(注解版本)前面的都不变,只是改下代码 web.xml 1234567<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app> <display-name>Archetype Created Web Application</display-name></web-app> index.jsp 12345<html><body><h2>Hello World!</h2></body></html> 运行环境一:在命令行运行tomcat HelloWorld.java 12345678910111213141516171819202122232425262728293031323334353637383940package com.mmzcg.annotation;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类@WebServlet("/hello")public class HelloWorld extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "注解执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + message + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("注解我就没见过销毁过"); }} Version.java 12345678910111213141516171819202122232425262728293031323334353637383940package com.mmzcg.annotation;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类@WebServlet("/version")public class Version extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + "注解返回的是版本号" + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("我就没见过销毁过"); }} 中文乱码Tomcat配置文件apache-tomcat-8.5.69\conf\server.xml 添加URIEncoding=”UTF-8” 属性 1234<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/> Idea指定编码 Jsp指定编码在jsp的html最先添加 1<%@ **page** language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> httpResponse指定编码12345678// 备注:该方法只对POST方式提交的数据有效,对GET方式提交的数据无效!request.setCharacterEncoding("UTF-8");//在响应中主动告诉浏览器使用UTF-8编码格式来接收数据response.setHeader("Content-Type", "text/html;charset=UTF-8");//可以使用封装类简写Content-Type,使用该方法则无需使用setCharacterEncodingresponse.setContentType("text/html;charset=UTF-8");

2022-11-19
Mybatis
教程 参考: 官方文档 源码 Provider的使用1.一定要注意type的类名.class 和 method方法名,还要注意形参也得是一样的 2.Provider的方法,大概就三个方法sql.SELECT,sql.WHERE,sql.FROM 3.SQL 对象里的方法名跟别的不一样,小写的不行,idea也识别不到,要用大写,比如SLELECT 4.Provider里返回的是String 注解 一对多查询(多个参数) 注解 一对多查询(多个参数)123456789101112131415161718192021222324252627282930313233343536373839404142@SelectProvider(method="queryPageFloors",type=BarterGroupProvider.class)@Results({ @Result(property = "userId",column="user_id"), @Result(property = "floorNum",column="floor_num"), @Result(property = "floorSecond",column="id"), @Result(property = "say",column="note"), @Result(property = "joinOrPublish",column="join_or_publish"), @Result(property="categorys",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.queryCategory")), @Result(property="productIds",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.queryProducts")), @Result(property="beforeResult",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupEvaluationMapper.queryAll")), @Result(property="barterGroupUser",javaType = BarterGroupUser.class,column="{user_id=user_id,id = barter_group_id }",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.isHead")), @Result(property="futureResultNum",column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.futureResultNum"))})List<QueryFloors> queryFloors3(Page<QueryFloors> page);/** * 分类 * @param id * @return */@Select("SELECT * FROM ecloud_marketing.barter_group_category WHERE barter_group_details_id = #{id} limit 0,7 ")List<BarterGroupCategory> queryCategory(@Param("id") Long id);/** * 产品 * @param id * @return */@Select("SELECT product_id from ecloud_marketing.barter_group_product_category WHERE barter_group_details_id = #{id} limit 0,3 ")List<Long> queryProducts(@Param("id") Long id);/** * 团员 * @param userId * @param id * @return */@Select("SELECT * FROM ecloud_marketing.barter_group_user WHERE user_id =#{user_id} and barter_group_id = #{id} and status = 1")BarterGroupUser isHead(Map<String,Object> map);@Select("SELECT count(*) from ecloud_marketing.barter_group_evaluation WHERE barter_group_details_id = #{id}")Integer futureResultNum(@Param("id") Long id); 1234567891011121314151617181920212223242526272829303132333435363738public class QueryFloors implements Serializable { @ApiModelProperty("我有的产品的id") List<ProductBarter> list; @ApiModelProperty("封装前的评论数据") private List<BarterGroupEvaluation> beforeResult; @ApiModelProperty("封装后的评论数据有分页给功能") private Page<BarterGroupEvaluation> beforeResultPage; @ApiModelProperty("封装后的评论数据") private List<BarterGroupEvaluationResult> futureResult; @ApiModelProperty("剩余评论条数") private Integer futureResultNum; @ApiModelProperty("分类") List<BarterGroupCategory> categorys; @ApiModelProperty(value="楼层数") private Integer floorNum; @ApiModelProperty(value="楼层id") private Long floorSecond; @ApiModelProperty(value="要说的") private String say; @ApiModelProperty(value="加入或者是发布") private Integer joinOrPublish; @ApiModelProperty("会员表") private BarterGroupUser barterGroupUser; @ApiModelProperty(value="加入时间") private Long joinData;} 注解 一对多查询(一个参数) 标签mybatis的xml的常用标签: include和sql标签 12345678<sql id="query_column"> id,user_no</sql><select id="test"> select <include refid="query_column"></include> from user_info</select> where标签 12345678910<select id="test"> select * from user_info where <if test="userName != null and userName != ''"> user_name = #{userName} </if> <if test="password != null and password != ''"> and password = #{password} </if></select> 如果userName= null,则sql语句就变成 1select * from user_info where and password = #{password} where标签可以去除where语句中的第一个and 或 or。 1234567891011<select id="test"> select * from user_info <where> <if test="userName != null and userName != ''"> and user_name = #{userName} </if> <if test="password != null and password != ''"> and password = #{password} </if> </where></select> set标签 123456789101112<update id="myupdate"> update user_info <set> <if test="userName != null"> user_name = #{userName}, </if> <if test="password != null"> password = #{password,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> trim标签 where标签只能去除第一个and或or,不够灵活,trim标签功能更加强大。 trim标签的属性如下: prefix 在trim标签内容前面加上前缀 suffix 在trim标签内容后面加上后缀 prefixOverrides 移除前缀内容。即 属性会忽略通过管道分隔的文本序列,多个忽略序列用 “|” 隔开。它带来的结果就是所有在 prefixOverrides 属性中指定的内容将被移除。 suffixOverrides 移除前缀内容。 12345678<trim prefix="where" prefixOverrides="and"> <if test="userId != null"> user_id=#{userId} </if> <if test="pwd != null and pwd !=''"> user_id=#{userId} </if></trim> foreach标签 foreach元素的属性主要有item,index,collection,open,separator,close. collection 要做foreach的对象,作为入参时,List对象默认用”list”代替作为键,数组对象有”array”代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param(“keyName”)来设置键,设置keyName后,list,array将会失效。 除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List ids。入参是User对象,那么这个collection = “ids”.如果User有属性Ids ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = “ids.id“,必填 item 合中元素迭代时的别名,必选 index 在list和数组中,index是元素的序号,在map中,index是元素的key,可选 open foreach代码的开始符号,一般是(和close=”)”合用。常用在in(),values()时。可选 separator 元素之间的分隔符,例如在in()的时候,separator=”,”会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样,可选。 close foreach代码的关闭符号,一般是)和open=”(“合用。常用在in(),values()时。可选 choose,when,otherwise标签 功能有点类似于Java中的 swicth - case - default 12345678910111213141516<select id="getUser" resultType="com.cat.pojo.User"> SELECT * FROM user <where> <choose> <when test="id != null and test.trim() != '' "> id = #{id} </when> <when test="name != null and name.trim() != '' "> name = #{name} </when> <otherwise> age = 17 </otherwise> </choose></where></select> if标签 123456<!-- 判断字符串--><if test="item != null and item != ''"></if><!-- 如果是Integer类型的需要把and后面去掉或是加上or--><if test="item != null"></if><if test="item != null and item != '' or item == 0"></if> 存过/函数存过 12345678<select id="pNextSupperUsers" parameterType="map" statementType="CALLABLE" resultType="vo.UserAgentVO"> { call p_next_supper_users( #{type,mode=IN,jdbcType=INTEGER}, #{userId,mode=IN,jdbcType=BIGINT} ) }</select> 123456List<UserAgentVO> pNextSupperUsers(Map<String, Object> param);Map<String, Object> param = new HashMap<>();param.put("type", 1);param.put("userId", userId);List<UserAgentVO> list = userInfoMapper.pNextSupperUsers(param); 函数 1234SELECT fn_next_user_count ( 1, u.id ) AS teamCount,FROMuser_info 源码参考 b站博学谷 架构设计 启动测试方法123456789101112131415161718192021# 第一种调用方法public static void test(){ String resource = "com/analyze/mybatis/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA"); sqlSession.close();}# 第二种调用方法public static void test2(){ String resource = "com/analyze/mybatis/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); Map map = userMapper.getUA(); sqlSession.close();} 下面的代码,大概意思就是能加载的配置文件的信息,解释 InputStream inputStream = Resources.getResourceAsStream(resource);这行代码的作用 读取mybatis的配置文件12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849// org.apache.ibatis.io.Resources#getResourceAsStream(java.lang.String)public static InputStream getResourceAsStream(String resource) throws IOException { return getResourceAsStream(null, resource);}// org.apache.ibatis.io.Resources#getResourceAsStream(java.lang.ClassLoader, java.lang.String)public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException { InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader); if (in == null) { throw new IOException("Could not find resource " + resource); } return in;}// org.apache.ibatis.io.ClassLoaderWrapper#getResourceAsStream(java.lang.String, java.lang.ClassLoader)public InputStream getResourceAsStream(String resource, ClassLoader classLoader) { return getResourceAsStream(resource, getClassLoaders(classLoader));}// org.apache.ibatis.io.ClassLoaderWrapper#getClassLoadersClassLoader[] getClassLoaders(ClassLoader classLoader) { return new ClassLoader[]{ classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(), getClass().getClassLoader(), systemClassLoader};}// org.apache.ibatis.io.ClassLoaderWrapper#getResourceAsStream(java.lang.String, java.lang.ClassLoader[])// 找到一个可以用的ClassloaderInputStream getResourceAsStream(String resource, ClassLoader[] classLoader) { for (ClassLoader cl : classLoader) { if (null != cl) { // try to find the resource as passed InputStream returnValue = cl.getResourceAsStream(resource); if (null == returnValue) { returnValue = cl.getResourceAsStream("/" + resource); } if (null != returnValue) { return returnValue; } } } return null;} 下面的代码,解释SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);,用来解析全局配置文件和解析mapper文件 下面是解析全局配置文件 解析全局配置文件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null);}// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 1.创建XpathParse解析器对象,根据inputStream解析成Document对象 // 2.创建全局配置Configuration对象 // 使用构建者模式,好处降低偶尔,分离复杂对象的创建 // 构建XMLConfig XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 根据Xpath解析xml配置文件,将配置文件封装到Configuration对象 // 返回DefaultSqlSessionFactory对象 // parse() 就是配置文件解析完成了 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } }}// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)// 最终返回public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(java.io.InputStream, java.lang.String, java.util.Properties)public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { // 点this,查看代码 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { // 创建Configuration对象,并通过TypeAliasRegistry注册一些Mybatis内部相关类的别名 super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser;}// org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.InputStream, boolean, java.util.Properties, org.xml.sax.EntityResolver)public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { commonConstructor(validation, variables, entityResolver); // 创建解析器 this.document = createDocument(new InputSource(inputStream));}// org.apache.ibatis.parsing.XPathParser#createDocument// 不用太关系,只是创建一个解析器的对象,顺便检查xml文档有没有写错private Document createDocument(InputSource inputSource) { // important: this must only be called AFTER common constructor try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(validation); factory.setNamespaceAware(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(false); factory.setCoalescing(false); factory.setExpandEntityReferences(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(entityResolver); builder.setErrorHandler(new ErrorHandler() { @Override public void error(SAXParseException exception) throws SAXException { throw exception; } @Override public void fatalError(SAXParseException exception) throws SAXException { throw exception; } @Override public void warning(SAXParseException exception) throws SAXException { } }); // 解析器的源码了,跟mybatis没有关系了 return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); } }// org.apache.ibatis.builder.xml.XMLConfigBuilder#parsepublic Configuration parse() { if (parsed) { // 每一个配置文件,只能解析一次 throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 从根节点开始解析,最终封装到Configuration对象 parseConfiguration(parser.evalNode("/configuration")); return configuration;}// org.apache.ibatis.parsing.XPathParser#evalNode(java.lang.String)public XNode evalNode(String expression) { return evalNode(document, expression);}// org.apache.ibatis.parsing.XPathParser#evalNode(java.lang.Object, java.lang.String)public XNode evalNode(Object root, String expression) { Node node = (Node) evaluate(expression, root, XPathConstants.NODE); if (node == null) { return null; } return new XNode(this, node, variables);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration// 可以配置这些信息:https://mybatis.org/mybatis-3/zh/configuration.htmlprivate void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases")); // 插件 pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 重点 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 重点 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); }}// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElementprivate void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // 三个值是互斥的 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); // 专门用来解析mapper映射文件 InputStream inputStream = Resources.getUrlAsStream(url); // 重点 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } }} 解释下environment作用,就是<environment id="development">这里的,不同的环境不同的配置 1234567891011121314151617181920212223<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://192.168.3.12:3306/orangedb"/> <property name="username" value="root"/> <property name="password" value="abcd2022"/> </dataSource> </environment> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.3.12:3306/orangedb"/> <property name="username" value="root"/> <property name="password" value="abcd2022"/> </dataSource> </environment> </environments></configuration> 用法:如下代码 1SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream,"development"); 下面是解析mapper文件,配置的属性,参考:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html 解析mapper配置文件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223// org.apache.ibatis.builder.xml.XMLMapperBuilder#XMLMapperBuilder(java.io.InputStream, org.apache.ibatis.session.Configuration, java.lang.String, java.util.Map<java.lang.String,org.apache.ibatis.parsing.XNode>)// 这个方法,前面已经用过了 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); }// org.apache.ibatis.builder.xml.XMLMapperBuilder#parsepublic void parse() { // mapper映射文件是否已经加载过 if (!configuration.isResourceLoaded(resource)) { // 从根节点解析 configurationElement(parser.evalNode("/mapper")); // 标记已经解析 configuration.addLoadedResource(resource); // 为命名空间绑定映射 bindMapperForNamespace(); } // parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements();}// org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElementprivate void configurationElement(XNode context) { try { // 获取命名空间 String namespace = context.getStringAttribute("namespace"); // 命名空间不能为空 if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } // 设置当前的命名空间的值 // 构建mappedStatement对象 builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); // 重点 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); }}// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } // 没有配置过getDatabaseId,所以走这里 buildStatementFromContext(list, null);}// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>, java.lang.String)private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } }}// org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNodepublic void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); // 没有设置过databaseId if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } String nodeName = context.getNode().getNodeName(); // 解析Sql命令类型是什么,确实是 Select,update,insert,delete 类型 SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); // 配置语言驱动 String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } // 替换占位符?,保存#{}里面的内容 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String resultType = context.getStringAttribute("resultType"); Class<?> resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap"); String resultSetType = context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); String resultSets = context.getStringAttribute("resultSets"); // 通过构建者助手,创建mappedstatement对象 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }// org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement(java.lang.String, org.apache.ibatis.mapping.SqlSource, org.apache.ibatis.mapping.StatementType, org.apache.ibatis.mapping.SqlCommandType, java.lang.Integer, java.lang.Integer, java.lang.String, java.lang.Class<?>, java.lang.String, java.lang.Class<?>, org.apache.ibatis.mapping.ResultSetType, boolean, boolean, boolean, org.apache.ibatis.executor.keygen.KeyGenerator, java.lang.String, java.lang.String, java.lang.String, org.apache.ibatis.scripting.LanguageDriver, java.lang.String)public MappedStatement addMappedStatement( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 创建MappedStatement对象 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } // 封装好MappedStatement,并返回 MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }// org.apache.ibatis.builder.MapperBuilderAssistant#applyCurrentNamespacepublic String applyCurrentNamespace(String base, boolean isReference) { if (base == null) { return null; } if (isReference) { // is it qualified with any namespace yet? if (base.contains(".")) { return base; } } else { // is it qualified with this namespace yet? if (base.startsWith(currentNamespace + ".")) { return base; } if (base.contains(".")) { throw new BuilderException("Dots are not allowed in element names, please remove it from " + base); } } // namespacename+点+方法名 return currentNamespace + "." + base; } 到这里,配置文件就解析完成了,下面是创建SqlSessionFactory对象 SqlSession1234 // org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);} 下面是mybatis创建sql的流程 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475// /Users/anthony/.m2/repository/org/mybatis/mybatis/3.5.2/mybatis-3.5.2-sources.jar!/org/apache/ibatis/builder/xml/XMLStatementBuilder.java:96// 占位符是如果进行替换的?动态sql如果进行的解析String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// org.apache.ibatis.builder.xml.XMLStatementBuilder#getLanguageDriverprivate LanguageDriver getLanguageDriver(String lang) { Class<? extends LanguageDriver> langClass = null; if (lang != null) { langClass = resolveClass(lang); } return configuration.getLanguageDriver(langClass);}// org.apache.ibatis.session.Configuration#getLanguageDriverpublic LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) { if (langClass == null) { return languageRegistry.getDefaultDriver(); } languageRegistry.register(langClass); return languageRegistry.getDriver(langClass);}// org.apache.ibatis.scripting.LanguageDriverRegistry#getDefaultDriver// 打断点可以看到是:XMLLanguageDriverpublic LanguageDriver getDefaultDriver() { return getDriver(getDefaultDriverClass());}public Class<? extends LanguageDriver> getDefaultDriverClass() { return defaultDriverClass;}// org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode();}// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#XMLScriptBuilder(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) { super(configuration); this.context = context; this.parameterType = parameterType; // 初始化动态sql的节点处理器结合 initNodeHandlerMap();}// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#initNodeHandlerMap private void initNodeHandlerMap() { nodeHandlerMap.put("trim", new TrimHandler()); nodeHandlerMap.put("where", new WhereHandler()); nodeHandlerMap.put("set", new SetHandler()); nodeHandlerMap.put("foreach", new ForEachHandler()); nodeHandlerMap.put("if", new IfHandler()); nodeHandlerMap.put("choose", new ChooseHandler()); nodeHandlerMap.put("when", new IfHandler()); nodeHandlerMap.put("otherwise", new OtherwiseHandler()); nodeHandlerMap.put("bind", new BindHandler()); }// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode// 解析sql,还有参数类型和结果集类型public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource;} 全部就解析完成了,接下来 看看session = sqlSessionFactory.openSession(); 创建事务对象 创建了执行器对象CasheingExecutor 创建DefaultSqlSession对象 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()@Overridepublic SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}// org.apache.ibatis.session.Configuration#getDefaultExecutorType// protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;public ExecutorType getDefaultExecutorType() { return defaultExecutorType;}// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource// 参数一:执行器,参数二:隔离级别private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 获取数据源环境信息 final Environment environment = configuration.getEnvironment(); // 获取事务工厂 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 获取JdbcTransaction或者ManagedTransaction // ManagedTransaction 就相当于没有事务 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 创建Executor执行器 final Executor executor = configuration.newExecutor(tx, execType); // 创建DefaultSqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}// org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory#newTransaction(java.sql.Connection)@Overridepublic Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit);}// org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType) public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // 如果允许缓存,会通过CachingExecutor去代理一层 if (cacheEnabled) { executor = new CachingExecutor(executor); } // 拦截器插件 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }// org.apache.ibatis.executor.CachingExecutor#CachingExecutorpublic CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this);} 调用具体的API进行查询用启动类中的方法,对比可以发现两段代码不同之处为: 1234Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA");UserMapper userMapper = sqlSession.getMapper(UserMapper.class);Map map = userMapper.getUA(); 在查看DefaultSqlSession中的selectOne方法,会执行以下的调用链 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205// org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)@Overridepublic <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; }}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)// RowBounds 分页对象public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT);}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 根据传入的statementId,获取mapperStatement对象 MappedStatement ms = configuration.getMappedStatement(statement); // 调用执行器的方法 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)// 注意这里是CachingExecutor,默认的@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 获取带问号的sql语句,比如 select * from user_info where id = ? BoundSql boundSql = ms.getBoundSql(parameterObject); // 生成缓存key CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 二级缓存 // usermapper的标签里可以设置<Cache>缓存标签 Cache cache = ms.getCache(); if (cache != null) { // 刷新二级缓存,在<select> 标签里可以配置flushcache flushCacheIfRequired(ms); // 在<select> 标签里可以配置usecache if (ms.isUseCache() && resultHandler == null) { // 判断是不是存过 ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") // 从二级缓存中查询数据 List<E> list = (List<E>) tcm.getObject(cache, key); // 如果从二级缓存没有查询到数据 if (list == null) { // 委托给BaseExecutor执行 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 将查询结果保存到二级缓存中,这里只是存到map集合中,没有真正存到二级缓存中 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } // 如果配置了FlushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存 if (queryStack == 0 && ms.isFlushCacheRequired()) { // 清空缓存 clearLocalCache(); } List<E> list; try { queryStack++; // 从一级缓存中获取数据 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { // 如果有数据,则处理本地缓存结果给输出参数 // 还是处理存过 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { // 没有缓存结果,则从数据库查询结果 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list;}// org.apache.ibatis.executor.BaseExecutor#queryFromDatabaseprivate <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 向本地缓存中存入一个ExecutionPlaceholder的枚举类占位 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 执行查询 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // 执行玩移除这个key localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list;}// org.apache.ibatis.executor.SimpleExecutor#doQuery@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { // 获取全局配置文件实例 Configuration configuration = ms.getConfiguration(); // new一个statementHandler实例 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 准备处理器,主要包括创建statement以及动态参数的设置 stmt = prepareStatement(handler, ms.getStatementLog()); // 执行真正的数据库操作调用 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); }}// org.apache.ibatis.session.Configuration#newStatementHandlerpublic StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 创建路由功能的StatementHanlder,根据MappedStatement中的StetementType StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // 插件机制:对核心对象进行拦截 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler;}// org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandlerpublic RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); }}// org.apache.ibatis.executor.SimpleExecutor#prepareStatementprivate Statement prepareStatement(StatementHandler handler, Log statementLog){ Statement stmt; // 获取代理后,增加日志功能的Connection对象 Connection connection = getConnection(statementLog); // 创建Statement对象 stmt = handler.prepare(connection, transaction.getTimeout()); // 参数化处理 handler.parameterize(stmt); return stmt;}// org.apache.ibatis.executor.statement.PreparedStatementHandler#query @Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps);} 解析结果1234567891011121314151617181920212223242526272829303132333435363738// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets@Overridepublic List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults);} 缓存1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253// org.apache.ibatis.executor.CachingExecutor#createCacheKey// CacheKey重新了 hacode和equals方法@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { // delegate 是具体类型的执行器的应用 // 默认是SimpleExecutor return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);}// org.apache.ibatis.executor.BaseExecutor#createCacheKey@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } // 通过构造器创建 CacheKey cacheKey = new CacheKey(); // id cacheKey.update(ms.getId()); // 分页参数 cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); // sql cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // mimic DefaultParameterHandler logic for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 参数的值 cacheKey.update(value); } } if (configuration.getEnvironment() != null) { // issue #176 // 当前环境的值 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey;} 面试题

2022-11-19
Mycat
主从复制启动1 .下载jre包 2 下载mycat包 3 打包镜像 Dockerfile 1234567891011121314FROM centos# 解压这个包到home下面ADD server-jre-8u251-linux-x64.tar.gz /home/ADD ./Mycat-server-1.6.7.4-release-20200105164103-linux.tar.gz /homeENV WORKPATH /home/mycat/WORKDIR $WORKPATHENV JAVA_HOME /home/jdk1.8.0_251ENV CLASSPATH $JAVA_HOME/dt.jar:$JAVA_HOME/lib/tools.jarENV PATH $PATH:$JAVA_HOME/BIN:$CATALINA_HOME/lib:$CATALINA_HOME/bin# 暴露8066端口EXPOSE 8066CMD /home/mycat/bin/mycat console 把mycat里的conf 和 logs 从容器中拷贝出来 再挂在两个目录,并启动 123456789101112131415docker run -it \ --name mycat \ -p 8066:8066 \ -v /home/anthony/mycat/conf:/home/mycat/conf/ \ -v /home/anthony/mycat/logs:/home/mycat/logs/ \ java1234/mycat:1.0Running Mycat-server...Removed stale pid file: /home/mycat/logs/mycat.pidwrapper | --> Wrapper Started as Consolewrapper | Launching a JVM...jvm 1 | Wrapper (Version 3.2.3) http://wrapper.tanukisoftware.orgjvm 1 | Copyright 1999-2006 Tanuki Software, Inc. All Rights Reserved.jvm 1 |jvm 1 | MyCAT Server startup successfully. see logs in logs/mycat.log 配置 schems.xml 定义逻辑库,表,分片节点等内容 rule.xml 定义分片规则 server.xml 定义用户以及系统相关变量 安装mysql5.7123# 从容器中拷贝这些文件docker cp xxx:/etc/mysql/mysql.conf.d/ /home/anthony/mysqldocker cp xxx:/var/log/ /home/anthony/mysql 启动的时候映射这些 创建网络12345678910# 可以查看容器的虚拟ipdocker inspect ac7# 查看网络docker network ls# 自定义一个网络模式# extnetwork 是网络模式的名字# 172.20.0.1 是网关docker network create --subnet=172.20.0.0/16 extnetwork mysql,mycat指定网路,重建容器12345678910111213141516171819202122232425262728# 指定网络模式和ip地址--net extnetwork --ip 172.20.0.2docker run -itd \ --name mysql-master \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v /home/anthony/mysql/mysql.conf.d/:/etc/mysql/conf.d/ \ -v /home/anthony/mysql/log/:/var/log/ \ --net extnetwork --ip 172.20.0.2 \ mysql:5.7docker run -itd \ --name mysql-slave \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v /home/anthony/mysql2/mysql.conf.d/:/etc/mysql/conf.d/ \ -v /home/anthony/mysql2/log/:/var/log/ \ --net extnetwork --ip 172.20.0.3 \ mysql:5.7docker run -it \ --name mycat \ -p 8066:8066 \ -v /home/anthony/mycat/conf:/home/mycat/conf/ \ -v /home/anthony/mycat/logs:/home/mycat/logs/ \ --net extnetwork --ip 172.20.0.4 \ java1234/mycat:1.0 主从配置主,在[mysqld] 12345678910111213# 主服务器ID,必须唯一server-id=2# 开启和设置二进制日志文件名称log_bin=mysql-bin# 要同步的数据库binlog-do-db=db_java1234# 不需要同步的数据库binlog-ignore_db=mysqlbinlog-ignore_db=information_schemabinlog-ignore_db=performation_schemabinlog_ignore_db=sys# 设置logbin格式,mysql默认采用statement,建议使用mixedbinlog_format=MIXED 从,在[mysqld] 12345678910111213141516# 主服务器ID,必须唯一server-id=3# 这行是从库需要添加的relay-log=mysql-relay# 开启和设置二进制日志文件名称log_bin=mysql-bin# 要同步的数据库binlog-do-db=db_java1234# 不需要同步的数据库binlog-ignore_db=mysqlbinlog-ignore_db=information_schemabinlog-ignore_db=performation_schemabinlog_ignore_db=sys# 设置logbin格式,mysql默认采用statement,建议使用mixedbinlog_format=MIXED 测试主从还没有生效 12345678910mysql> show master status;+------------------+----------+--------------+--------------------------------------------------+-------------------+| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |+------------------+----------+--------------+--------------------------------------------------+-------------------+| mysql-bin.000003 | 357 | db_java1234 | mysql,information_schema,performation_schema,sys | |+------------------+----------+--------------+--------------------------------------------------+-------------------+1 row in set (0.01 sec)mysql> show slave status;Empty set 设置用户名在master执行 12345678910111213# 创建用户名为slave1# 172.20.0.3从库ip# 123456 密码CREATE USER 'slave1'@'172.20.0.3' IDENTIFIED BY '123456';# 语法:GRANT privileges ON databasename.tablename TO 'username'@'host'# 172.20.0.3 从库# 两个权限:REPLICATION和SLAVE# *.* 所有的库和表GRANT REPLICATION SLAVE ON *.* TO 'slave1'@'172.20.0.3';# 刷新权限FLUSH PRIVILEGES; 在slave执行 1234CHANGE MASTER TO MASTER_HOST='172.20.0.2',MASTER_USER='slave1',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000003',MASTER_LOG_POS=964;# 开启主从start slave; 在从库执行 1show slave status; 主要看Slave_io_running =yes 主要看Slave_sql_running =yes 读写分离scheml.xmlschema标签1234567<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1"> <!-- auto sharding by id (long) --> <!--splitTableNames 启用<table name 属性使用逗号分割配置多个表,即多个表使用这个配置--> <table name="travelrecord,address" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" splitTableNames ="true"/> <!-- <table name="oc_call" primaryKey="ID" dataNode="dn1$0-743" rule="latest-month-calldate" /> --></schema> name: 配置逻辑库的名称 checkSQLschema: 当执行select * from T_Account.user 为true时,mycat会把sql转成select * from user false 会报错 sqlMaxLimit:最大查询每页条数,如果sql指定了limit,就以sql为准,没有,就以配置为准 randomDataNode:这个先不用,先改成datanote='dn1' datanote标签123<dataNode name="dn1" dataHost="localhost1" database="db1" /><dataNode name="dn2" dataHost="localhost1" database="db2" /><dataNode name="dn3" dataHost="localhost1" database="db3" /> name:数据分片节点名称 dataHost:定义该数据分片节点属于哪台数据库主机 database:指定哪个数据库,db_java1234 datahost标签123456789<dataHost name="localhost1" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- can have multi write hosts --> <writeHost host="hostM1" url="localhost:3306" user="root" password="123456"> </writeHost> <!-- <writeHost host="hostM2" url="localhost:3316" user="root" password="123456"/> --></dataHost> name:跟datanote标签的datahost对应 maxCon 指定每个读写实例连接池的的最大链接,write合readhost标签都会使用这个属性的值来实例化出连接池的最大连接数 min 最小的连接 balance: 0 不开启读写分离,所有读操作都发送到当前可用的writehost上 1 2 所有读操作都随机在writehost合readehost上分发 3 所有读请求随机分发到writehost合readhos 双组双从第二套主从1.在mysql添加中添加 123log-slave-update=1auto-increment-increment=2auto-increment-offset=1 2.把mysql文件复制一份mysql3 3.把mysql3的配置文件修修改这些配置 123# 主服务器ID,必须唯一server-id=5auto-increment-offset=2 4.再创建两个容器,m2 和 s2 master 和 master同步就是相互slave 测试读写分离插入语句 1insert into test valus(@@hostname)

2023-12-10
Netty
Server 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162// 把 NettyServer 的创建交给 Spring 管理@Componentpublic class NettyServer { private Logger logger = LoggerFactory.getLogger(getClass()); @Value("${netty.port}") private Integer port; @Resource private NettyServerHandlerInitializer nettyServerHandlerInitializer; // boss 线程组,用于服务端接受客户端的连接 private EventLoopGroup bossGroup = new NioEventLoopGroup(); // worker 线程组,用于服务端接受客户端的数据读写 private EventLoopGroup workerGroup = new NioEventLoopGroup(); // Netty Server Channel private Channel channel; // 启动 Netty Server @PostConstruct public void start() throws InterruptedException { // 创建 ServerBootstrap 对象,用于 Netty Server 启动 ServerBootstrap bootstrap = new ServerBootstrap(); // 设置 ServerBootstrap 的各种属性 // 设置两个 EventLoopGroup 对象 bootstrap.group(bossGroup, workerGroup) // 指定 Channel 为服务端 NioServerSocketChannel .channel(NioServerSocketChannel.class) // 设置 Netty Server 的端口 .localAddress(new InetSocketAddress(port)) // 服务端 accept 队列的大小 .option(ChannelOption.SO_BACKLOG, 1024) // TCP Keepalive 机制,实现 TCP 层级的心跳保活功能 .childOption(ChannelOption.SO_KEEPALIVE, true) // 允许较小的数据包的发送,降低延迟 .childOption(ChannelOption.TCP_NODELAY, true) // 处理器 .childHandler(nettyServerHandlerInitializer); // 绑定端口,并同步等待成功,即启动服务端 ChannelFuture future = bootstrap.bind().sync(); if (future.isSuccess()) { channel = future.channel(); logger.info("[start][Netty Server 启动在 {} 端口]", port); } } // 关闭 Netty Server @PreDestroy public void shutdown() { // 关闭 Netty Server if (channel != null) { channel.close(); } // <3.2> 优雅关闭两个 EventLoopGroup 对象 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }} NettyServerHandlerInitializer 12345678910111213141516171819202122232425262728293031@Componentpublic class NettyServerHandlerInitializer extends ChannelInitializer<Channel> { /** * 心跳超时时间 */ private static final Integer READ_TIMEOUT_SECONDS = 3 * 60; @Resource private NettyServerHandler nettyServerHandler; @Override protected void initChannel(Channel ch) { // 获得 Channel 对应的 ChannelPipeline ChannelPipeline channelPipeline = ch.pipeline(); // 添加一堆 NettyServerHandler 到 ChannelPipeline 中 channelPipeline // 空闲检测 .addLast(new ReadTimeoutHandler(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)) // 编码器 .addLast(new InvocationEncoder()) // 解码器 .addLast(new InvocationDecoder()) // 消息分发器 .addLast(messageDispatcher) // 服务端处理器 .addLast(nettyServerHandler) ; }}

2022-11-19
Spring Security
Spring Security1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency> 登录业务 1234567891011121314151617181920212223242526272829303132333435363738@Servicepublic class LoginService { public static Map<String, RedisUser> map = new HashMap<>(); @Resource AuthenticationManager authenticationManager; public String login(SysUser sysUser) { // AuthenticationManager 进行用户认证 UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword()); Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken); // 如果认证不通过,给出对应的提示 if (Objects.isNull(authenticate)) { throw new RuntimeException("登录失败"); } // 如果认证通过,使用username给jwt生成一个 RedisUser principal = (RedisUser) authenticate.getPrincipal(); map.put(principal.getUsername(), principal); return createJWT("1234567", 9990000, principal.getUsername()); } public void logout() { UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); RedisUser principal = (RedisUser) authentication.getPrincipal(); String username = principal.getUsername(); // 从redis里删除 map.remove(username); }} 123456789101112131415161718192021222324@RestController@RequestMapping("/")public class LoginController { @Resource LoginService loginService; @PostMapping("/user/login") public ResponseEntity<HashMap> login(@RequestParam("username") String username,@RequestParam("password") String password){ SysUser sysUser = new SysUser(); sysUser.setUsername(username); sysUser.setPassword(password); HashMap<String, Object> stringObjectHashMap = new HashMap<>(); stringObjectHashMap.put("token", loginService.login(sysUser)); return ResponseEntity.status(200).body(stringObjectHashMap); } @PostMapping("/user/logout") public ResponseEntity<String> logout(){ loginService.logout(); return ResponseEntity.ok("success"); }} 验证账户 12345678910111213141516171819202122232425262728293031323334353637383940414243import com.example.spirngsecutirylearn.pojo.RedisUser;import com.example.spirngsecutirylearn.pojo.SysUser;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Service;import java.util.ArrayList;@Servicepublic class TestService implements UserDetailsService { static String user = "anthony"; static String password = "$2a$10$yyR9WuT9JY/bpe1VPU0yguqlv0lWpgzTD9NEetf2.n8y7NXIa1rfm"; /** * DaoAuthenticationProvier 会调用这个方法查询用户,并且返回UserDetails对象 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库查询用户信息 SysUser sysUser = new SysUser(); if (username.equals(user)) { sysUser.setUsername(user); sysUser.setPassword(password); }else { // 没有查到用户 throw new RuntimeException("没有查到用户信息"); } // 查询对应的权限信息 ArrayList<String> strings = new ArrayList<>();// strings.add("test"); strings.add("admin"); // 返回封装的信息 return new RedisUser(sysUser,strings); }} 缓存的用户对象 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263package com.example.spirngsecutirylearn.pojo;import lombok.AllArgsConstructor;import lombok.Data;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;import java.util.List;import java.util.stream.Collectors;@Datapublic class RedisUser implements UserDetails { SysUser sysUser; List<String> list; public RedisUser(SysUser sysUser, List<String> list) { this.sysUser = sysUser; this.list = list; } /** * 权限 * @return */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { return list.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); } @Override public String getPassword() { return sysUser.getPassword(); } @Override public String getUsername() { return sysUser.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; }} 认证失败的回调 123456789101112131415161718192021222324252627282930313233package com.example.spirngsecutirylearn.handler;import cn.hutool.json.JSONUtil;import org.springframework.http.ResponseEntity;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AuthException implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { ResponseEntity<String> responseEntity = ResponseEntity.ok().body("认证失败,请重新登录"); String str = JSONUtil.toJsonStr(responseEntity); /// 处理异常 extracted(response,str); } private static void extracted(HttpServletResponse response,String string) throws IOException { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().println(string); }} 授权失败的回调 123456789101112131415161718192021222324252627282930313233package com.example.spirngsecutirylearn.handler;import cn.hutool.json.JSONUtil;import org.springframework.http.ResponseEntity;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import org.springframework.security.web.access.AccessDeniedHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AccessException implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { ResponseEntity<String> responseEntity = ResponseEntity.ok().body("权限不租"); String str = JSONUtil.toJsonStr(responseEntity); /// 处理异常 extracted(response,str); } private static void extracted(HttpServletResponse response,String string) throws IOException { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().println(string); }} 开启注解权限控制和自定义注解 1234567891011121314151617181920@RestController@RequestMapping("/test")public class TestController { @GetMapping("/hello") @PreAuthorize("hasAnyAuthority('test')") public ResponseEntity<String> hello(){ return ResponseEntity.ok("hello server"); } /** * 自定义校验权限 * @return */ @GetMapping("/hello2") @PreAuthorize("@anthony.hasAnyAuthority('test')") public ResponseEntity<String> hello2(){ return ResponseEntity.ok("hello server2"); }} 配置 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100package com.example.spirngsecutirylearn.cofig;import com.example.spirngsecutirylearn.filter.JwtFilter;import com.example.spirngsecutirylearn.handler.AccessException;import com.example.spirngsecutirylearn.handler.AuthException;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.config.http.SessionCreationPolicy;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import org.springframework.security.web.authentication.logout.LogoutFilter;import javax.annotation.Resource;@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource JwtFilter jwtFilter; @Resource AuthException authException; @Resource AccessException accessException; @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity // CSRF禁用,因为不使用session .csrf().disable() // 基于token,所以不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() // 过滤请求 .authorizeRequests() // 对于登录login 注册register 验证码captchaImage 允许匿名访问 .antMatchers("/user/login").anonymous() // 静态资源,permitAll 有没有登录都访问// .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()// .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated();// .and()// .headers().frameOptions().disable(); // 添加Logout filter// httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);// // 添加JWT filter httpSecurity.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);// // 添加CORS filter// httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);// httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); // 添加自定义的认证和授权的自定义失败处理 httpSecurity.exceptionHandling().authenticationEntryPoint(authException).accessDeniedHandler(accessException); httpSecurity.cors(); } /** * 密码加密的规则 * @return */ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * BCryptPasswordEncoder 的测试方法 * @param args */ public static void main(String[] args) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); // 加密 String encode = bCryptPasswordEncoder.encode("123456"); System.out.println(encode); // 解密 boolean matches = bCryptPasswordEncoder.matches("123456", encode); System.out.println(matches); }} 自定义注解 123456789101112131415161718@Component("anthony")public class TestExpress { public boolean hasAnyAuthority(String authority){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); RedisUser principal = (RedisUser) authentication.getPrincipal();// List<String> list = (List<String>)principal.getAuthorities(); List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) principal.getAuthorities(); for (SimpleGrantedAuthority simpleGrantedAuthority : authorities) { if (simpleGrantedAuthority.getAuthority().equals(authority)) { return true; } } return false; }} 找Java包的路径 12# mac/usr/libexec/java_home -V
评论