MyBatis intervju pitanja, 23 MyBatis ključna pitanja (6000 reči, 30 crteža), mora pročitati
6400 reči 30 crteža, detaljno objašnjeno 23 najčešća MyBatis intervju pitanja, mora pročitati. Organizovao: Chenmo Wang Er, preuzeto sa: originalni članak.
Nastavljamo seriju Mianzheni, ovaj put naš glavni junak je MyBatis. Kao trenutno najpopularniji ORM framework u Kini, ovo je najkorisniji alat za nas CRUD programere. Hajde da vidimo koja se pitanja postavljaju na intervjuima.
Osnove
1. Šta je MyBatis?

Prvo predstavljanje:
- Mybatis je polu-ORM (Object Relational Mapping) framework, interno je enkapsulirao JDBC, pri razvoju se fokusira samo na SQL izjave, ne treba trošiti energiju na procese učitavanja drajvera, kreiranja konekcije, kreiranja statement i sl. Programeri direktno pišu prirodni SQL, mogu strogo da kontroliraju performanse SQL izvršenja, velika fleksibilnost.
- MyBatis može koristiti XML ili anotacije za konfiguraciju i mapiranje izvornih informacija, mapira POJO u zapise baze podataka, izbegljava gotovo sav JDBC kod i ručno postavljanje parametara i dobijanje rezultata.
Potom mane
- Veći posao kod pisanja SQL izjava, naročito kada je polja mnogo, tabela povezanih mnogo, zahteva se od programera dobro poznavanje SQL pisanja
- SQL izjave zavise od baze podataka, loša prenosivost baze podataka, ne može se slobodno menjati baza podataka
Šta je ORM?

- ORM (Object Relational Mapping), mapiranje objektno-relacionih veza, tehnologija za rešavanje relacije mapiranja između relacionih baza podataka i jednostavnih Java objekata (POJO). Jednostavno rečeno, ORM koristi metapodatke koji opisuju mapiranje između objekata i baze podataka da automatski perzistira objekte iz programa u relacionu bazu podataka.
Zašto se MyBatis smatra polu-automatskim ORM alatom? Gde je razlika u poređenju sa potpuno automatskim?
- Hibernate pripada potpuno automatskom ORM alatu za mapiranje, kada koristite Hibernate za upit povezanih objekata ili kolekcija povezanih objekata, možete ih direktno dobiti na osnovu modela objektnih relacija, zato je potpuno automatski.
- Dok MyBatis prilikom upita povezanih objekata ili kolekcija povezanih objekata zahteva ručno pisanje SQL da bi se završilo, zato se naziva polu-automatskim ORM alatom za mapiranje.
Koji nedostaci postoje kod JDBC programiranja, kako ih MyBatis rešava?

- Često kreiranje i oslobađanje konekcija sa podacima prouzrokuje gubitak resursa sistema i utiče na performanse sistema, u mybatis-config.xml konfigurišete pool konekcija sa podacima, koristite pool za jedinstveno upravljanje konekcijama ka bazi podataka.
- SQL izjave napisane u kodu čine kod teškim za održavanje, konfigurišete SQL izjave u XXXXmapper.xml fajlovima odvojeno od java koda.
- Teško je prosleđivati parametre SQL izjavama, jer uslov where u SQL izjavi nije siguran, može biti više ili manje, placeholder-i moraju odgovarati parametarima jedan-po-jedan. MyBatis automatski mapira java objekte na SQL izjave.
- Teško je raščlanjivati skupove rezultata, promena SQL uzrokuje promenu koda raščlanjivanja, i pre raščlanjivanja treba iterirati, ako se zapisi baze podataka mogu enkapsulirati u pojo objekte, raščlanjivanje je povećaje. MyBatis automatski mapira rezultate SQL izvršenja na java objekte.
2. Koje su razlike između Hibernate i MyBatis?
Slične strane
- Oba su enkapsulacija jdbc-a, oba se primenjuju u sloju persistance.

Različite strane
- Odnos mapiranja
- MyBatis je framework polu-automatskog mapiranja, konfiguriše odgovarajući odnos između java objekata i rezultata SQL izvršenja, konfiguracija višetabličnih relacionih veza je jednostavna
- Hibernate je framework kompletnog mapiranja tabela, konfiguriše odgovarajući odnos između java objekata i tabela baze podataka, konfiguracija višetabličnih relacionih veza je komplikovana
- SQL optimizacija i prenosivost
- Hibernate enkapsulira SQL izjave, obezbeđuje logove, keš, kaskade (kaskade su moćnije od MyBatis) i druge karakteristike, dodatno nudi HQL (Hibernate Query Language) za operacije baze podataka, dobra podrška za nezavisnost od baze podataka, ali troši više performansi. Ako projekat treba da podržava više vrsta baza podataka, razvoj koda je manji, ali je teško optimisati SQL izjave.
- MyBatis zahteva ručno pisanje SQL, podržava dinamički SQL, procesiranje lista, dinamičko generisanje imena tabela, podržava stored procedure. Razvoj je relativno teži. Direktno koristi SQL izjave za operacije baze podataka, ne podržava nezavisnost od baze podataka, ali je SQL optimizacija laka.
- Različite scenario primene MyBatis i Hibernate

- Hibernate je standardni ORM framework, manje pisanja SQL, ali nije fleksibilan, pogodan za relativno stabilne zahteve, projekte malih i srednjih softverskih veličina, na primer: sistem za automatizaciju kancelarijskog posla
- MyBatis je polu-ORM framework, treba pisati više SQL, ali je fleksibilniji, pogodan za projekte sa čestim promenama zahteva i brzim iteracijama, na primer: e-commerce sajtovi
3. Proces korišćenja MyBatis? Životni ciklus?
Osnovni proces korišćenja MyBatis može se podeliti u nekoliko koraka:

- Kreiranje SqlSessionFactory
Može se kreirati iz konfiguracije ili direktnog kodiranja
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);- Kroz SqlSessionFactory kreirati SqlSession
SqlSession (sesija) može se razumeti kao most između programa i baze podataka
SqlSession session = sqlSessionFactory.openSession();- Izvršavanje operacija baze podataka kroz sqlsession
Možete direktno izvršiti mapirane SQL izjave kroz instancu SqlSession:
Blog blog = (Blog)session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);Češće korišćen način je prvo dobijanje Mapper (mapiranja), pa izvršenje SQL izjave:
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);- Pozivanje session.commit() da potvrdite transakciju
Ako su to update, delete izjave, još uvek treba potvrditi transakciju.
- Pozivanje session.close() da zatvorite sesiju
Na kraju ne zaboravite zatvoriti sesiju.
Ko je životni ciklus MyBatis?
Gore su pomenuti nekoliko komponenti MyBatis, opšte mišljenje je da se životni ciklus MyBatis odnosi na životni ciklus ovih komponenti.
- SqlSessionFactoryBuilder
Jednom kada se kreira SqlSessionFactory, više nije potreban. Stoga životni ciklus instance SqlSessionFactoryBuilder postoji samo unutar metoda.
- SqlSessionFactory
SqlSessionFactory služi za kreiranje SqlSession, ekvivalent pool-u konekcija baze podataka, svako kreiranje SqlSessionFactory troši resurse baze podataka, višestruko kreiranje i uništavanje je gubitak resursa. Stoga je SqlSessionFactory životnog ciklusa aplikacije, i treba biti singleton.
- SqlSession
SqlSession odgovara Connection-u u JDBC-u, instanca SqlSession nije thread-safe, stoga se ne može deliti, njen optimalni životni ciklus je jedan zahtev ili jedna metoda.
- Mapper
Mapper-i su interfejsi koji vezuju mapirane izjave. Instance interfejsa mapera dobijaju se iz SqlSession, životni ciklus je unutar transakcionih metoda sqlsession, obično se kontroliše na nivou metoda.

Naravno, sve se može integrisati sa Spring, MyBatis obično se koristi i integriše sa Spring, Spring može pomoći da kreiramo thread-safe, SqlSession i mapere bazirane na transakcijama, i da ih direktno ubacimo u naše bean-e, ne moramo brinuti o njihovom procesu kreiranja i životnom ciklusu, to je druga priča.

4. Kako proslediti više parametara u mapper?

Metod 1: Metod sekvencijalnog prosleđivanja parametara
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{0} and dept_id = #{1}
</select>\#{}brojevi unutra predstavljaju redosled prosleđenih parametara.- Ovaj metod se ne preporučuje, izražavanje na SQL sloju nije intuitivno, i jednom promenom redosleda lako je napraviti grešku.
Metod 2: Metod prosleđivanja parametara @Param anotacijom
public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>\#{}nazivi unutar odgovaraju nazivima modifikovanih u @Param zagradi.- Ovaj metod je relativno intuitivan kada parametara nema mnogo (preporučuje se).
Metod 3: Metod prosleđivanja parametara Map
public User selectUser(Map<String, Object> params);
<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>\#{}nazivi unutar odgovaraju ključevima unutar Map.- Ovaj metod je pogodan za prosleđivanje više parametara, i kada se parametri lako menjaju i mogu fleksibilno prosleđivati.
Metod 4: Metod prosleđivanja parametara Java Bean
public User selectUser(User user);
<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>\#{}nazivi unutar odgovaraju atributima članova klase User.- Ovaj metod je intuitivan, potreban je kreirati entitet klasu, proširenje nije lako, treba dodati atribute, ali čitljivost koda je jaka, obrada poslovne logike je pogodna, preporučuje se (preporučuje se).
5. Imena atributa entitet klase i imena polja u tabeli nisu isti, šta raditi?
- Prvi način: Definisanjem pseudonima imena polja u SQL izjavi upita, neka pseudonimi imena polja odgovaraju imenima atributa entitet klase.
<select id="getOrder" parameterType="int" resultType="com.jourwon.pojo.Order">
select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
</select>- Drugi način: Kroz <result> u resultMap-u da se mapira odgovarajući odnos između imena polja i imena atributa entitet klase.
<select id="getOrder" parameterType="int" resultMap="orderResultMap">
select * from orders where order_id=#{id}
</select>
<resultMap type="com.jourwon.pojo.Order" id="orderResultMap">
<!–koristiti id atribut za mapiranje polja primarnog ključa–>
<id property="id" column="order_id">
<!–koristiti result atribut za mapiranje polja koje nisu primarni ključ, property je ime atributa entitet klase, column je atribut u tabeli baze podataka–>
<result property ="orderno" column ="order_no"/>
<result property="price" column="order_price" />
</resultMap>6. Da li Mybatis može mapirati Enum enumeracije?
- Mybatis naravno može mapirati enumeracije, ne samo da može mapirati enumeracije, Mybatis može mapirati bilo koji objekat na kolonu tabele. Način mapiranja je prilagođavanje TypeHandler-a, implementacija setParameter() i getResult() interfejs metoda TypeHandler-a.
- TypeHandler ima dve uloge, jedna je završiti konverziju iz javaType u jdbcType, druga je završiti konverziju iz jdbcType u javaType, odražava se kao setParameter() i getResult() dve metode, koje predstavljaju postavljanje parametara placeholder-upita SQL i dobijanje rezultata upita kolone.
7. Koje su razlike između #{} i ${}?
#{} je pretprocesiranje, ${} je zamena stringova.
①, kada koristite #{}, MyBatis će pre izvršenja SQL zameniti placeholder-upita sa upitnikom ?, i koristiti vrednosti parametara da zamene ove upitnike.
Budući da #{} koristi pretprocesiranje, može efikasno sprečiti SQL injekciju, osiguravajući da se vrednosti parametara ispravno procesiraju i escaping-uju pre nego što stignu do baze podataka.
<select id="selectUser" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>②, kada koristite ${}, vrednost parametra će se direktno zameniti u SQL izjavu, neće proći kroz pretprocesiranje.
Ovo postoji rizik od SQL injekcije, jer se vrednost parametra direktno nadovezuje na SQL izjavu, ako je vrednost parametra 1 or 1=1, onda će SQL izjava postati SELECT * FROM users WHERE id = 1 or 1=1, ovako će rezultirati prikazom svih korisnika.
${} se obično koristi za situacije kada se ne može koristiti pretprocesiranje, na primer dinamička imena tabela, imena kolona, sortiranje itd., potrebno je unapred verifikovati sigurnost parametara.
<select id="selectUsersByOrder" resultType="User">
SELECT * FROM users ORDER BY ${columnName} ASC
</select>8. Kako napisati izraz fuzzy upita like?

- 1 '
%${question}%' može izazvati SQL injekciju, ne preporučuje se - 2 '"%"#{question}"%"' obratite pažnju: jer
#{…}prilikom raščlanjivanja na SQL izjavu automatski dodaje jednostruke navodnike '' oko promenljive, stoga ovde % treba koristiti dvostruke navodnike "", ne može se koristiti jednostruki navodnik ', inače neće pronaći nikakav rezultat. - 3
CONCAT('%',#{question},'%')koristi CONCAT() funkciju (preporučuje se ✨) - 4 Korišćenje bind tag-a (ne preporučuje se)
<select id="listUserLikeUsername" resultType="com.jourwon.pojo.User">
  <bind name="pattern" value="'%' + username + '%'" />
  select id,sex,age,username,password from person where username LIKE #{pattern}
</select>9. Da li Mybatis može izvršiti one-to-one, one-to-many asocijativne upite?
Naravno može, ne samo podržava one-to-one, one-to-many asocijativne upite, već podržava i many-to-many, many-to-one asocijativne upite.

- One-to-one <association>
Na primer, narudžbina i plaćanje su one-to-one odnos, realizacija ovakve asocijacije:
Entitet klase:
public class Order {
private Integer orderId;
private String orderDesc;
/**
* Objekat plaćanja
*/
private Pay pay;
//……
}Mapiranje rezultata
<!-- resultMap narudžbine -->
<resultMap id="peopleResultMap" type="cn.fighter3.entity.Order">
<id property="orderId" column="order_id" />
<result property="orderDesc" column="order_desc"/>
<!--mapiranje one-to-one rezultata-->
<association property="pay" javaType="cn.fighter3.entity.Pay">
<id column="payId" property="pay_id"/>
<result column="account" property="account"/>
</association>
</resultMap>Upit je običan asocijativni upit
<select id="getTeacher" resultMap="getTeacherMap" parameterType="int">
select * from order o
left join pay p on o.order_id=p.order_id
where o.order_id=#{orderId}
</select>- One-to-many <collection>
Na primer, kategorija proizvoda i proizvodi su one-to-many odnos.
- Entitet klase
public class Category {
private int categoryId;
private String categoryName;
/**
* Lista proizvoda
**/
List<Product> products;
//……
}- Mapiranje rezultata
<resultMap type="Category" id="categoryBean">
<id column="categoryId" property="category_id" />
<result column="categoryName" property="category_name" />
<!-- one-to-many odnos -->
<!-- property: odnosi se na vrednost atributa kolekcije, ofType: odnosi se na tip elemenata u kolekciji -->
<collection property="products" ofType="Product">
<id column="product_id" property="productId" />
<result column="productName" property="productName" />
<result column="price" property="price" />
</collection>
</resultMap>- Upit
Upit je običan asocijativni upit
<!-- asocijativni upit tabele kategorije i tabele proizvoda -->
<select id="listCategory" resultMap="categoryBean">
select c.*, p.* from category_ c left join product_ p on c.id = p.cid
</select> Znači many-to-one, many-to-many kako se realizuje? Isto koriste <association> i <collection>, zbog ograničenja prostora, ovde se ne širi.
10. Da li Mybatis podržava odloženo učitavanje? Princip?
- Mybatis podržava odloženo učitavanje association asocijativnih objekata i collection asocijativnih kolekcija objekata, association se odnosi na one-to-one, collection se odnosi na one-to-many upite. U konfiguracionom fajlu MyBatis, možete konfigurisati da li je omogućeno odloženo učitavanje lazyLoadingEnabled=true|false.
- Njegov princip je korišćenje CGLIB-a za kreiranje proxy objekta ciljnog objekta, kada se pozove ciljna metoda, ulazi se u metodu interceptrora, na primer poziv a.getB().getName(), metoda interceptrora invoke() otkriva da je a.getB() null vrednost, onda će zasebno poslati prethodno sačuvani SQL upit za asocijativni B objekat, B će se dovesti, onda poziva a.setB(b), tako atribut b objekta a dobija vrednost, zatim se završava poziv metoda a.getB().getName(). Ovo je osnovni princip odloženog učitavanja.
- Naravno, ne samo MyBatis, skoro svi uključujući Hibernate, princip podrške odloženog učitavanja je isti.
11. Kako dobiti generisani primarni ključ?
- U oznaku dodajte: keyProperty=" ID" i to je to
<insert id="insert" useGeneratedKeys="true" keyProperty="userId" >
insert into user(
user_name, user_password, create_time)
values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
</insert>- Tada možete završiti vraćanje primarnog ključa
mapper.insert(user);
user.getId;12. Da li MyBatis podržava dinamički SQL?
MyBatis ima nekoliko tag-ova koji podržavaju dinamički SQL, njihov princip je korišćenje OGNL za računanje vrednosti izraza iz objekta SQL parametara, na osnovu vrednosti izraza dinamički spaja SQL, time se završava funkcija dinamičkog SQL.

- if
Sastavlja where klauzulu na osnovu uslova
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = 'ACTIVE'
<if test="title != null">
AND title like #{title}
</if>
</select>- choose (when, otherwise)
Ovo je malo kao switch izjava u Java
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>trim (where, set)
<where> može koristiti kada su svi uslovi upita dinamički
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>- <set> može koristiti prilikom dinamičkog ažuriranja
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>- foreach
Kada vidite ime, znate da ovo služi za ciklično izvršenje, može iterirati kroz kolekcije
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>13. Kako MyBatis izvršava batch operacije?

Prvi metod: Korišćenje foreach tag-a
foreach se uglavnom koristi u građenju in uslova, u SQL izjavi može iterirati kroz kolekciju. Atributi foreach tag-a su uglavnom item, index, collection, open, separator, close.
- item predstavlja alias svakog elementa prilikom iteracije kroz kolekciju, slobodno ime promenljive;
- index navodi ime, za predstavljanje pozicije svake iteracije u procesu iteracije, retko se koristi;
- open označava čime počinje izjava, obično "(";
- separator označava simbol koji se koristi kao separator između svake iteracije, obično ",";
- close označava čime se završava, obično ")".
Kada koristite foreach, najkritičnije i najlakše za grešku je atribut collection, ovaj atribut se mora specificirati, ali u različitim situacijama, vrednost ovog atributa nije ista, uglavnom postoji sledećih 3 situacija:
- Ako se prosledi jedan parametar i tip parametra je List, vrednost atributa collection je list
- Ako se prosledi jedan parametar i tip parametra je array niz, vrednost atributa collection je array
- Ako se prosledi više parametara, treba ih enkapsulirati u Map, naravno jedan parametar se takođe može enkapsulirati u map, zapravo ako u prosleđivanju parametara, u MyBatis-u će se takođe enkapsulirati u Map, ključ map-e je ime parametra, stoga u ovo vreme vrednost atributa collection je ključ List ili array objekta u sopstvenoj enkapsuliranoj map-i
Pogledajte dva načina batch čuvanja:
<!-- Batch čuvanje pod MySQL, može foreach iterirati MySQL podržava values(),(),() sintaksu --> //preporučuje se
<insert id="addEmpsBatch">
INSERT INTO emp(ename,gender,email,did)
VALUES
<foreach collection="emps" item="emp" separator=",">
(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
</foreach>
</insert><!-- Ovaj način zahteva podršku atributa konekcije baze podataka allowMutiQueries=true
na primer jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true -->
<insert id="addEmpsBatch">
<foreach collection="emps" item="emp" separator=";">
INSERT INTO emp(ename,gender,email,did)
VALUES(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
</foreach>
</insert>Drugi metod: Korišćenje ExecutorType.BATCH
- Mybatis ugrađeni ExecutorType ima 3 vrste, podrazumevano je simple, u ovom režimu za svako izvršenje izjave kreira novu pretprocesiranu izjavu, pojedinačno slanje sql; dok batch režim ponovo koristi već pretprocesirane izjave, i batch izvršava sve update izjave, očigledno batch performanse su bolje; Ali batch režim ima svoje probleme, na primer u Insert operacijama, pre potvrde transakcije, nema načina da dobije auto-increment id, u određenim okolnostima ne zadovoljava poslovne zahteve.
Specifična upotreba:
//batch metoda čuvanja testa
@Test
public void testBatch() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//sqlSession koji može izvršavati batch operacije
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
//vreme pre batch čuvanja
long start = System.currentTimeMillis();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
for (int i = 0; i < 1000; i++) {
mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
}
openSession.commit();
long end = System.currentTimeMillis();
//vreme posle batch čuvanja
System.out.println("Vreme izvršenja" + (end - start));
//batch pretprocesiranje sql jednom==>postavljanje parametara==>10000 puta==>izvršavanje 1 put 677
//ne-batch (pretprocesiranje=postavljanje parametara=izvršavanje )==>10000 puta 1121
} finally {
openSession.close();
}
}- mapper i mapper.xml kao ispod
public interface EmployeeMapper {
//batch čuvanje zaposlenih
Long addEmp(Employee employee);
}<mapper namespace="com.jourwon.mapper.EmployeeMapper"
<!--batch čuvanje zaposlenih -->
<insert id="addEmp">
insert into employee(lastName,email,gender)
values(#{lastName},#{email},#{gender})
</insert>
</mapper>14. Koji su nivo-1, nivo-2 keš Mybatis?
- Nivo-1 keš: Lokalni keš baziran na HashMap-u PerpetualCache, opseg skladištenja je SqlSession, keš između različitih SqlSession je međusobno izolovan, kada se Session flush ili close, sav Cache u ovom SqlSession će se očistiti, MyBatis podrazumevano otvara nivo-1 keš.

- Nivo-2 keš je isti mehanizam kao nivo-1 keš, podrazumevano takođe koristi PerpetualCache, HashMap skladištenje, razlika je u opsegu skladištenja Mapper (Namespace), može se deliti između više SqlSession, i može se prilagoditi izvor skladištenja, na primer Ehcache. Podrazumevano ne otvara nivo-2 keš, za omogućavanje nivo-2 keša, klase atributa nivo-2 keša treba implementirati Serializable serial interfejs (može se koristiti za čuvanje stanja objekta), može se konfigurisati u njegovom map fajlu.

Principi
15. Možete li objasniti princip rada MyBatis?
Već smo približno znali proces rada MyBatis, prema principu rada, može se podeliti u dva velika koraka: generisanje fabrike sesije, izvršenje sesije.

MyBatis je zreli framework, zbog ograničenja prostora, ovde ćemo uzeti veliko i ostaviti malo, pogledajmo njegov glavni proces rada.
Izgradnja fabrike sesije
Konstrukcija fabrike sesije se može podeliti u dva koraka:

- Dobavljanje konfiguracije
Korak dobavljanja konfiguracije je prošao kroz nekoliko transformacija, konačno je generisana instanca klase konfiguracije, ova instanca klase konfiguracije je vrlo važna, glavne uloge uključuju:
- Čitanje konfiguracionih fajlova, uključujući osnovne konfiguracione fajlove i map fajlove
- Inicijalizacija osnovne konfiguracije, na primer alias MyBatis-a, i druge važne objekte klase, poput plugin-a, mapera, ObjectFactory itd.
- Pružanje singleton-a, kao važnog parametra za izgradnju fabrike sesije
- Njegov proces izgradnje takođe inicijalizuje neke promenljive okruženja, na primer datasource
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
//izostavljeno rukovanje izuzecima
//xml konfiguracioni građevinski builder
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//kroz Configuration transformisanu izgradnju SqlSessionFactory
var5 = this.build(parser.parse());
}- Izgradnja SqlSessionFactory
SqlSessionFactory je samo interfejs, zapravo se grada instanca njegove implementacione klase, obično koristimo njegovu implementacionu klasu DefaultSqlSessionFactory,
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}Izvršenje sesije
Izvršenje sesije je najkomplikovaniji deo MyBatis, njen rad ne može bez saradnje četiri glavne komponente:

- Executor (izvršilac)
Executor igra ključnu ulogu, SqlSession je samo fasada, ekvivalent korisničkoj službi, pravi rad je Executor, poput nevidljivog inženjera. Pruža odgovarajuće metode upita i ažuriranja, i transakcione metode.
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//kroz Configuration kreiranje executor-a
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);- StatementHandler (handler baze podataka sesije)
StatementHandler, kako ime kaže, procesira sesiju baze podataka. Uzmimo SimpleExecutor za primer, pogledajmo njegovu metodu upita, prvo generiše instancu StatementHandler, pa uzme ovaj handler da izvrši query.
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}Zatim uzmemo najčešće korišćen PreparedStatementHandler da pogledamo njegovu query metodu, zapravo u gornjem prepareStatement su već parametri bili pretprocesirani, do ovoga, direktno izvršava sql, koristi ResultHandler za procesiranje povratnih rezultata.
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}- ParameterHandler (procesor parametara)
U PreparedStatementHandler-u sql je bio pretprocesiran
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}Ovde se koristi ParameterHandler, uloga setParameters je postavljanje parametara pretprocesirane SQL izjave.
Unutra će se takođe koristiti typeHandler procesor tipa, za procesiranje tipova.
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement var1) throws SQLException;
}- ResultSetHandler (procesor rezultata)
Videli smo i gore, konačni rezultat treba procesuirati kroz ResultSetHandler, metoda handleResultSets služi za pakovanje skupa rezultata. Mybatis nam je obezbedio DefaultResultSetHandler, obično se koristi ova implementaciona klasa za procesiranje rezultata.
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement var1) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;
void handleOutputParameters(CallableStatement var1) throws SQLException;
}On će koristiti typeHandle za procesiranje tipova, zatim koristi pravila ObjectFactory za sklapanje objekata, vraća pozivaocu.
Ukupno, sumirajmo izvršenje sesije:

Konačno, povežemo ceo proces rada, jednostavno sumirajmo:

- Čitanje MyBatis konfiguracionog fajla——mybatis-config.xml, učitavanje map fajla——map fajl je SQL map fajl, fajl je konfigurisao SQL izjave za operacije baze podataka. Konačno generiše jedan konfiguracioni objekat.
- Konstrukcija fabrike sesije: kroz informacije o okruženju MyBatis i druge konfiguracije gradi fabriku sesije SqlSessionFactory.
- Kreiranje objekta sesije: fabrika sesije kreira objekat SqlSession, ovaj objekat sadrži sve metode za izvršenje SQL izjava.
- Executor izvršilac: MyBatis na dnu definiše Executor interfejs za operacije baze podataka, dinamički generiše SQL izjave koje treba izvršiti na osnovu parametara prosleđenih kroz SqlSession, istovremeno je odgovoran za održavanje keša upita.
- StatementHandler: sesija handler-a baze podataka, povezuje procesiranje mapiranja parametara i procesiranje mapiranja rezultata izvršenja.
- Procesiranje parametara: procesiranje tipova ulaznih parametara, i pretprocesiranje.
- Procesiranje rezultata: procesiranje tipova povratnih rezultata, na osnovu pravila mapiranja objekata, vraća odgovarajuće objekte.
16. Kako izgleda arhitektura funkcionalnosti MyBatis?

Opšte mišljenje je da arhitekturu funkcionalnosti Mybatis delimo na tri sloja:
- API interfejs sloj: obezbeđuje spoljašnji API interfejs, programeri kroz ove lokalne API-e manipulišu bazom podataka. Interfejs sloj čim primi poziv poziva sloj za procesiranje podataka da završi specifično procesiranje podataka.
- Sloj procesiranja podataka: odgovoran je za specifične SQL pretrage, SQL analizu, SQL izvršenje i procesiranje mapiranja rezultata izvršenja itd. Njegov glavni cilj je završiti jednu operaciju baze podataka na osnovu poziva.
- Sloj osnovne podrške: odgovoran je za najosnovniju funkcionalnu podršku, uključujući upravljanje konekcijama, upravljanje transakcijama, učitavanje konfiguracije i procesiranje keša, ovo su sve deljene stvari, izvlačimo ih kao najosnovnije komponente. Pruža najosnovniju podršku gornjem sloju procesiranja podataka.
17. Zašto Mapper interfejs ne zahteva implementacionu klasu?
Četiri reči odgovora: dinamički proxy, pogledajmo proces dobijanja Mapper-a:

- Dobavljanje Mapper-a
Svi znamo da definisani Mapper interfejs nema implementacionu klasu, Mapper mapiranje se zapravo realizuje kroz dinamički proxy.
BlogMapper mapper = session.getMapper(BlogMapper.class);Uđimo malo dokle god možemo, otkrivamo da proces dobijanja Mapper-a zahteva prvo dobijanje MapperProxyFactory——fabrika Mapper proxy-ja.
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}- MapperProxyFactory
Uloga MapperProxyFactory je generisanje MapperProxy (objekat Mapper proxy-ja).
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
……
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}Ovde se može videti vezivanje dinamičkog proxy-ja za interfejs, njegova uloga je generisanje objekta dinamičkog proxy-ja (placeholder), metoda proxy-ja je stavljena u MapperProxy.
- MapperProxy
U MapperProxy, obično se generiše objekat MapperMethod, on se inicijalizuje kroz metodu cachedMapperMethod, zatim izvršava excute metodu.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}- MapperMethod
U MapperMethod metoda excete će stvarno izvršiti sql. Ovde se koristi komandni pattern, zapravo kruži oko, konačno se koristi instanca SqlSession da pokrene sql objekta.
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
……
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
……
}18.Koji Executor izvršioci postoje u Mybatis?

Mybatis ima tri osnovna Executor izvršioca, SimpleExecutor, ReuseExecutor, BatchExecutor.
- SimpleExecutor: Svaki put kada izvršava update ili select, otvara Statement objekat, nakon upotrebe odmah zatvara Statement objekat.
- ReuseExecutor: Izvršava update ili select, traži Statement objekat koristeći sql kao ključ, ako postoji koristi, ako ne postoji kreira, nakon upotrebe ne zatvara Statement objekat, nego stavlja u Map<String, Statement>, za sledeću upotrebu. Ukratko, ponovo koristi Statement objekat.
- BatchExecutor: Izvršava update (nema select, JDBC batch podrška ne podržava select), dodaje sve sql u batch (addBatch()), čeka na jedinstveno izvršenje (executeBatch()), kešira više Statement objekata, svaki Statement objekat je nakon završetka addBatch(), čeka na pojedinačno izvršenje executeBatch() batch procesiranja. Isto kao JDBC batch procesiranje.
Opseg primene: Ove karakteristike Executor-a su strogo ograničene na životni ciklus SqlSession.
Kako u Mybatis specificirati koji će se Executor izvršilac koristiti?
- U Mybatis konfiguracionom fajlu, u podešavanjima (settings) možete specificirati podrazumevani tip ExecutorType izvršioca, takođe možete ručno proslediti parametar tipa ExecutorType metodi kreiranja SqlSession klase DefaultSqlSessionFactory, na primer
SqlSession openSession(ExecutorType execType). - Konfigurišite podrazumevani izvršilac. SIMPLE je običan izvršilac; REUSE izvršilac će ponovo koristiti pretprocesirane izjave; BATCH izvršilac će ponovo koristiti izjave i izvršavati batch ažuriranja.
Plugin-i
19. Možete li objasniti princip rada plugin-a MyBatis, kako napisati plugin?
Princip rada plugin-a?
Izvršenje sesije MyBatis zahteva saradnju četiri velika objekta ParameterHandler, ResultSetHandler, StatementHandler, Executor, princip plugin-a je u vreme raspoređivanja ovih četiri velika objekta, ubaciti naše kodove.

Mybatis koristi dinamički proxy JDK-a, generiše proxy objekat za ciljni objekat. Obezbeđuje alatnu klasu Plugin, implementira InvocationHandler interfejs.

Korišćenje Plugin za generisanje proxy objekta, kada proxy objekat poziva metodu, ulazi se u invoke metodu, u invoke metodi, ako postoji potpisana metoda presretanja, intercept metoda plugin-a će ovde biti pozvana, zatim se vraća rezultat. Ako ne postoji metoda potpisa, onda će se direktno refleksivno pozvati metodu koju želimo da izvršimo.
Kako napisati plugin?
Mi sami pišemo MyBatis plugin, samo treba implementirati interfejs presretača Interceptor (org.apache.ibatis. plugin Interceptor ), u implementacionoj klasi procesirati objekat i metodu presretanja.
- Implementirati MyBatis Interceptor interfejs i prevazideti intercept() metodu
Ovde smo samo štampali pre i posle ciljnog objekta koji izvršava ciljnu metodu;
public class MyInterceptor implements Interceptor {
Properties props=null;
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("before……");
//ako se trenutno proxy-ira ne-proxy objekat, onda će pozvati metodu stvarnog objekta presretanja
// ako ne, onda će pozvati metodu invoke sledećeg plugin proxy objekta
Object obj=invocation.proceed();
System.out.println("after……");
return obj;
}
}- Zatim napišite anotaciju za plugin, odredite objekat presretanja, metodu presretanja
@Intercepts({@Signature(
type = Executor.class, //odredite objekat presretanja
method = "update", //odredite metodu presretanja
args = {MappedStatement.class,Object.class} //parametri metode presretanja
)})
public class MyInterceptor implements Interceptor {
Properties props=null;
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("before……");
//ako se trenutno proxy-ira ne-proxy objekat, onda će pozvati metodu stvarnog objekta presretanja
// ako ne, onda će pozvati metodu invoke sledećeg plugin proxy objekta
Object obj=invocation.proceed();
System.out.println("after……");
return obj;
}
}- Konačno, u MyBatis konfiguracionom fajlu konfigurišite plugin
<plugins>
<plugin interceptor="xxx.MyPlugin">
<property name="dbType",value="mysql"/>
</plugin>
</plugins>20. Kako MyBatis radi straničenje? Koji je princip plugin-a za straničenje?
Kako MyBatis radi straničenje?
MyBatis koristi RowBounds objekat za straničenje, ovo je memorijsko straničenje usmereno na ResultSet rezultat set, a ne fizičko straničenje. Može se direktno napisati SQL sa parametrima fizičkog straničenja da bi se završila funkcija fizičkog straničenja, takođe može koristiti plugin za straničenje da bi se završilo fizičko straničenje.
Koji je princip plugin-a za straničenje?
- Osnovni princip plugin-a za straničenje je korišćenje Mybatis interfejsa plugin-a, implementacija prilagođenog plugin-a, presretanje query metoda Executor-a
- Prilikom izvršenja upita, presreće se sql koji treba izvršiti, zatim se prepisuje sql, na osnovu dialect dijalekta, dodaje odgovarajuća izjava fizičkog straničenja i parametri fizičkog straničenja.
- Primer:
select * from student, nakon presretanja sql se prepisuje kao:select t.* from (select * from student) t limit 0, 10
Možete pogledati opšti MyBatis presretač za straničenje:

Dodatak
21. Koji su koraci izvršenja JDBC?
2024 03 19 dodatak
Java database konekcija (JDBC) je Java API za izvršenje SQL izjava, obezbeđuje jedinstven mehanizam pristupa za više relacija baza podataka. Korišćenje JDBC za operacije baze podataka obično uključuje sledeće korake:
Prvi korak, učitavanje drajvera baze podataka
Pre uspostavljanja konekcije sa bazom podataka, prvo treba kroz Class.forName() metodu učitati odgovarajući drajver baze podataka. Ovaj korak osigurava da se JDBC drajver registruje u DriverManager klasi.
Class.forName("com.mysql.cj.jdbc.Driver");Drugi korak, uspostavljanje konekcije sa bazom podataka
Korišćenjem DriverManager.getConnection() metode za uspostavljanje konekcije ka bazi podataka. Ovaj korak zahteva prosleđivanje URL-a baze podataka, korisničkog imena i lozinke kao parametara.
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/databaseName", "username", "password");Treći korak, kreiranje Statement objekta
Kroz uspostavljeni objekat konekcije baze podataka Connection kreirajte Statement, PreparedStatement ili CallableStatement objekat, za izvršenje SQL izjava.
Statement stmt = conn.createStatement();Ili kreirajte PreparedStatement objekat (pretprocesirana SQL izjava, pogodna za SQL sa parametrima):
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM tableName WHERE column = ?");
pstmt.setString(1, "value");Četvrti korak, izvršenje SQL izjava
Korišćenje Statement ili PreparedStatement objekta za izvršenje SQL izjava.
Prilikom izvršenja upita (SELECT) izjave, koristite executeQuery() metodu, ona vraća ResultSet objekat;
Prilikom izvršenja ažuriranja (INSERT, UPDATE, DELETE) izjava, koristite executeUpdate() metodu, ona vraća integer koji predstavlja broj obuhvaćenih redova.
ResultSet rs = stmt.executeQuery("SELECT * FROM tableName");Ili
int affectedRows = stmt.executeUpdate("UPDATE tableName SET column = 'value' WHERE condition");Peti korak, procesiranje skupa rezultata
Ako se izvrši operacija upita, treba procesirati ResultSet objekat da bi se dobili podaci.
while (rs.next()) {
String data = rs.getString("columnName");
// procesiraj svaki red podataka
}Šesti korak, zatvaranje resursa
Konačno, treba redom zatvoriti ResultSet, Statement i Connection i druge resurse, osloboditi konekciju baze podataka i druge resurse.
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();U Java razvoju, obično se koristi JDBC template biblioteka (poput Spring JdbcTemplate) ili ORM framework (poput Hibernate, MyBatis, MyBatis-Plus) da bi se uprostile operacije baze podataka i upravljanje resursima.
22. Šta se dobija nakon kreiranja konekcije?
U koracima izvršenja JDBC, nakon kreiranja konekcije dobija se java.sql.Connection objekat. Ovaj objekat je interfejs u JDBC API za predstavljanje konekcije baze podataka, obezbeđuje metode za izvršenje SQL izjava, upravljanje transakcijama i druge operacije.
Connection objekat predstavlja jednu sesiju konekcije između aplikacije i baze podataka.
Pozivanjem DriverManager.getConnection() metode i prosleđivanjem informacija kao što su URL baze podataka, korisničko ime i lozinka dobija se ovaj objekat.
Jednom kada se dobije Connection objekat, može se koristiti za kreiranje Statement, PreparedStatement i CallableStatement objekata za izvršenje SQL izjava, kao i za upravljanje transakcijama itd.
23. Koje su razlike između Statement i PreparedStatement?
2024 03 19 dodatak
Statement i PreparedStatement su oba interfejsa za izvršenje SQL izjava, ali između njih postoji nekoliko ključnih razlika:
①, Svaki put kada se izvršava executeQuery ili executeUpdate metoda objekta Statement, SQL izjava na bazi podataka treba ponovo da se kompajlira i izvrši. Ovo je pogodno za jednom izvršavanje SQL izjava.
Statement ne podržava parametarske upite. Ako je potrebno ubaciti promenljive u SQL izjavu, obično se to radi spajanjem stringova, što povećava rizik od SQL injekcijom napada.
②, PreparedStatement predstavlja objekat pretprocesiranih SQL izjava. To znači da se SQL izjava šalje u bazu podataka za pretprocesiranje u vreme kreiranja PreparedStatement objekta.
Potom, više puta može efikasno izvršiti ovu SQL izjavu postavljanjem vrednosti parametara. Ovo ne samo smanjuje trošenje baze podataka na kompajliranje SQL izjava, već i poboljšava performanse, naročito za ponovljene SQL operacije.
PreparedStatement podržava parametarske upite, to jest može koristiti upitnik (?) kao placeholder parametra u SQL izjavi. Kroz setXxx metode (poput setString, setInt) postavljanje parametara, može efikasno sprečiti SQL injekciju.
Ukupno rečeno, PreparedStatement u poređenju sa Statement ima bolje performanse i višu sigurnost, preferirani način za izvršenje SQL izjava, naročito pri procesiranju dinamičkih upita sa korisničkim unosom.
24. Šta je SQL injekcija? Kako sprečiti SQL injekciju?
SQL injekcija je tehnika injekcije koda, kroz ubacivanje posvećenih SQL izjava u polja unosa, time obmanjuje bazu podataka da izvrši zlonameran SQL, kako bi dobila osetljive podatke, modifikovala podatke, ili obrisala podatke itd.
Na primer, postoji ovaj kod:
studentId = getRequestString("studentId");
lookupStudent = "SELECT * FROM students WHERE studentId = " + studentIdKorisnik unosi 117 u polje za upit:

Stvarna SQL izjava izgleda ovako:
SELECT * FROM students WHERE studentId = 117Ovo je način na koji očekujemo da korisnik ispravno unese. Ali, ako korisnik unese 117 OR 1=1, onda SQL izjava postaje:
SELECT * FROM students WHERE studentId = 117 OR 1=1Budući da je 1=1 tačno, ovaj upit će vratiti informacije o svim studentima, a ne samo studentu sa ID 117.

Da bi se sprečila SQL injekcija, mogu se preduzeti sledeće mere:
①, Korišćenje parametarskih upita
Korišćenje parametarskih upita, to jest korišćenje PreparedStatement objekta, kroz setXxx metode postavljanje vrednosti parametara, umesto spajanja SQL izjava stringovima. Ovo može efikasno sprečiti SQL injekciju.
String query = "SELECT * FROM users WHERE username = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, userName); // userName je korisnički unos
ResultSet rs = pstmt.executeQuery();? je placeholder parametra, userName je spoljni unos. Čak i ako korisnik unese zlonameranu SQL izjavu, smatraće se delom parametra, neće promeniti strukturu upita.
②, Ograničavanje korisničkog unosa
Verifikacija i filtriranje korisničkog unosa, dozvoljava se samo unos očekivanih podataka, ne dozvoljava se unos specijalnih karaktera ili SQL ključnih reči.
③, Korišćenje ORM framework-a
Na primer, u MyBatis, korišćenje #{} placeholder-a umesto direktnog spajanja SQL izjava, MyBatis će automatski procesirati parametarske upite.
<select id="selectUser" resultType="User">
SELECT * FROM users WHERE username = #{userName}
</select>Ako userName prosleđena vrednost je 9;DROP TABLE SYS_USER;, poslana SQL izjava za brisanje tabele neće biti izvršena, jer će se smatrati vrednošću parametra.
SELECT * FROM users WHERE username = '9;DROP TABLE SYS_USER;'Ništa me ne zadržava——osim cilja, čak i ako na obali ima ruža, hlada, mirne luke, ja sam brod bez veza.
Serijal sadržaja:
- Mianzheni Java SE 🡭
- Mianzheni Java kolekcijski framework 🡭
- Mianzheni Java konkurentno programiranje 🡭
- Mianzheni JVM 🡭
- Mianzheni Spring 🡭
- Mianzheni Redis 🡭
- Mianzheni MyBatis 🡭
- Mianzheni MySQL 🡭
- Mianzheni operativni sistemi 🡭
- Mianzheni računarske mreže 🡭
- Mianzheni RocketMQ 🡭
- Mianzheni distribuirani sistemi 🡭
- Mianzheni mikroservisi 🡭
- Mianzheni dizajn paterni 🡭
- Mianzheni Linux 🡭
- Mianzheni OpenClaw 🡭
- Mianzheni Vještine 🡭
GitHub sa 17000+ zvezda otvorena baza znanja [Mianzheni](https://github.com/itwanger/toBeBetterJavaer) drugo izdanje PDF je konačno stiglo! Uključuje Java osnove, Java kolekcijski framework, Java konkurentno programiranje, JVM, Spring, Redis, MyBatis, MySQL, operativni sistemi, računarske mreže, RocketMQ, distribuirani sistemi, mikroservisi, dizajn paterni, Linux, OpenClaw itd., ukupno 320+ hiljada reči, 500+ crteža, može se reći da je jasno i zanimljivo… Detalji: Mianzheni 2.0 izdanje PDF objavljeno, Java back-end programeri moraju znati, možda najbolje osnovne informacije 2026
